From 9ad19a2eaf031d95e9be1a6059aafb4790bed4ff Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Tue, 31 May 2016 14:57:31 -0700 Subject: [PATCH 01/93] Draw grab balls in front of entities (not just the lines as before). Don't intersect with grab balls (not just lasers as before). Don't run grab lasers through (2d or 3d) overlays. Don't try to manage reticle in handControllerGrab because other scripts do. --- .../system/controllers/handControllerGrab.js | 35 ++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 06549a38b5..be4bffe58c 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -249,6 +249,18 @@ function entityIsGrabbedByOther(entityID) { return false; } +// If another script is managing the reticle (as is done by HandControllerPointer), we should not be setting it here, +// and we should not be showing lasers when someone else is using the Reticle to indicate a 2D minor mode. +var EXTERNALLY_MANAGED_2D_MINOR_MODE = true; +function isIn2DMode() { + return EXTERNALLY_MANAGED_2D_MINOR_MODE && Reticle.visible; +} +function restore2DMode() { + if (!EXTERNALLY_MANAGED_2D_MINOR_MODE) { + Reticle.setVisible(true); + } +} + function MyController(hand) { this.hand = hand; if (this.hand === RIGHT_HAND) { @@ -302,7 +314,10 @@ function MyController(hand) { this.update = function() { this.updateSmoothedTrigger(); - + if (isIn2DMode()) { + _this.turnOffVisualizations(); + return; + } switch (this.state) { case STATE_OFF: this.off(); @@ -425,6 +440,8 @@ function MyController(hand) { color: color, alpha: SEARCH_SPHERE_ALPHA, solid: true, + ignoreRayIntersection: true, + drawInFront: true, // Even when burried inside of something, show it. visible: true } this.searchSphere = Overlays.addOverlay("sphere", sphereProperties); @@ -447,6 +464,8 @@ function MyController(hand) { color: color, alpha: 0.1, solid: true, + ignoreRayIntersection: true, + drawInFront: true, // Even when burried inside of something, show it. visible: true } this.grabSphere = Overlays.addOverlay("sphere", sphereProperties); @@ -477,6 +496,7 @@ function MyController(hand) { end: farPoint, color: color, ignoreRayIntersection: true, // always ignore this + drawInFront: true, // Even when burried inside of something, show it. visible: true, alpha: 1 }; @@ -490,6 +510,7 @@ function MyController(hand) { color: color, visible: true, ignoreRayIntersection: true, // always ignore this + drawInFront: true, // Even when burried inside of something, show it. alpha: 1 }); } @@ -759,8 +780,7 @@ function MyController(hand) { this.particleBeamOff(); } this.searchSphereOff(); - - Reticle.setVisible(true); + restore2DMode(); }; @@ -901,9 +921,16 @@ function MyController(hand) { } else { intersection = Entities.findRayIntersection(pickRayBacked, true); } + var overlayIntersection = Overlays.findRayIntersection(pickRayBacked); + if (!intersection.intersects || (overlayIntersection.intersects && (intersection.distance > overlayIntersection.distance))) { + intersection = overlayIntersection; + } + // If we want to share results with other scripts, this is where we would do it. if (intersection.intersects) { - rayPickedCandidateEntities.push(intersection.entityID); + if (intersection.entityID) { + rayPickedCandidateEntities.push(intersection.entityID); + } this.intersectionDistance = Vec3.distance(pickRay.origin, intersection.intersection); } } From c2858f847baea7f9fe8e8f5e161772f98e659ef0 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Tue, 31 May 2016 16:57:23 -0700 Subject: [PATCH 02/93] Partition vive trackpad by center, x, and y (not just center v outer). --- interface/resources/controllers/vive.json | 8 ++++---- .../src/controllers/StandardControls.h | 6 ++++-- plugins/openvr/src/ViveControllerManager.cpp | 20 ++++++++++++------- plugins/openvr/src/ViveControllerManager.h | 2 +- 4 files changed, 22 insertions(+), 14 deletions(-) diff --git a/interface/resources/controllers/vive.json b/interface/resources/controllers/vive.json index 60a46ba3ce..df78ce7e0d 100644 --- a/interface/resources/controllers/vive.json +++ b/interface/resources/controllers/vive.json @@ -1,16 +1,16 @@ { "name": "Vive to Standard", "channels": [ - { "from": "Vive.LY", "when": "Vive.LSOuter", "filters": ["invert"], "to": "Standard.LY" }, - { "from": "Vive.LX", "when": "Vive.LSOuter", "to": "Standard.LX" }, + { "from": "Vive.LY", "when": "Vive.LSY", "filters": ["invert"], "to": "Standard.LY" }, + { "from": "Vive.LX", "when": "Vive.LSX", "to": "Standard.LX" }, { "from": "Vive.LT", "to": "Standard.LT" }, { "from": "Vive.LeftGrip", "to": "Standard.LB" }, { "from": "Vive.LS", "to": "Standard.LS" }, { "from": "Vive.LSTouch", "to": "Standard.LSTouch" }, - { "from": "Vive.RY", "when": "Vive.RSOuter", "filters": ["invert"], "to": "Standard.RY" }, - { "from": "Vive.RX", "when": "Vive.RSOuter", "to": "Standard.RX" }, + { "from": "Vive.RY", "when": "Vive.RSY", "filters": ["invert"], "to": "Standard.RY" }, + { "from": "Vive.RX", "when": "Vive.RSX", "to": "Standard.RX" }, { "from": "Vive.RT", "to": "Standard.RT" }, { "from": "Vive.RightGrip", "to": "Standard.RB" }, diff --git a/libraries/controllers/src/controllers/StandardControls.h b/libraries/controllers/src/controllers/StandardControls.h index 79c23bc6ee..4e0269bef1 100644 --- a/libraries/controllers/src/controllers/StandardControls.h +++ b/libraries/controllers/src/controllers/StandardControls.h @@ -44,7 +44,8 @@ namespace controller { LS_TOUCH, LEFT_THUMB_UP, LS_CENTER, - LS_OUTER, + LS_X, + LS_Y, RIGHT_PRIMARY_THUMB, RIGHT_SECONDARY_THUMB, @@ -53,7 +54,8 @@ namespace controller { RS_TOUCH, RIGHT_THUMB_UP, RS_CENTER, - RS_OUTER, + RS_X, + RS_Y, LEFT_PRIMARY_INDEX, LEFT_SECONDARY_INDEX, diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 6e75454b5f..f006c436e1 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -284,20 +284,24 @@ void ViveControllerManager::InputDevice::handleHandController(float deltaTime, u } // pseudo buttons the depend on both of the above for-loops - partitionTouchpad(controller::LS, controller::LX, controller::LY, controller::LS_CENTER, controller::LS_OUTER); - partitionTouchpad(controller::RS, controller::RX, controller::RY, controller::RS_CENTER, controller::RS_OUTER); + partitionTouchpad(controller::LS, controller::LX, controller::LY, controller::LS_CENTER, controller::LS_X, controller::LS_Y); + partitionTouchpad(controller::RS, controller::RX, controller::RY, controller::RS_CENTER, controller::RS_X, controller::RS_Y); } } } -void ViveControllerManager::InputDevice::partitionTouchpad(int sButton, int xAxis, int yAxis, int centerPseudoButton, int outerPseudoButton) { +void ViveControllerManager::InputDevice::partitionTouchpad(int sButton, int xAxis, int yAxis, int centerPseudoButton, int xPseudoButton, int yPesudoButton) { // Populate the L/RS_CENTER/OUTER pseudo buttons, corresponding to a partition of the L/RS space based on the X/Y values. const float CENTER_DEADBAND = 0.6f; + const float DIAGONAL_DIVIDE_IN_RADIANS = PI / 4.0f; if (_buttonPressedMap.find(sButton) != _buttonPressedMap.end()) { float absX = abs(_axisStateMap[xAxis]); float absY = abs(_axisStateMap[yAxis]); - bool isCenter = (absX < CENTER_DEADBAND) && (absY < CENTER_DEADBAND); // square deadband - _buttonPressedMap.insert(isCenter ? centerPseudoButton : outerPseudoButton); + glm::vec2 cartesianQuadrantI(absX, absY); + float angle = glm::atan(cartesianQuadrantI.y / cartesianQuadrantI.x); + float radius = glm::length(cartesianQuadrantI); + bool isCenter = radius < CENTER_DEADBAND; + _buttonPressedMap.insert(isCenter ? centerPseudoButton : ((angle < DIAGONAL_DIVIDE_IN_RADIANS) ? xPseudoButton :yPesudoButton)); } } @@ -460,9 +464,11 @@ controller::Input::NamedVector ViveControllerManager::InputDevice::getAvailableI makePair(RS, "RS"), // Differentiate where we are in the touch pad click makePair(LS_CENTER, "LSCenter"), - makePair(LS_OUTER, "LSOuter"), + makePair(LS_X, "LSX"), + makePair(LS_Y, "LSY"), makePair(RS_CENTER, "RSCenter"), - makePair(RS_OUTER, "RSOuter"), + makePair(RS_X, "RSX"), + makePair(RS_Y, "RSY"), // triggers makePair(LT, "LT"), diff --git a/plugins/openvr/src/ViveControllerManager.h b/plugins/openvr/src/ViveControllerManager.h index bd5d4a39f4..e14a10a94e 100644 --- a/plugins/openvr/src/ViveControllerManager.h +++ b/plugins/openvr/src/ViveControllerManager.h @@ -61,7 +61,7 @@ private: void handleAxisEvent(float deltaTime, uint32_t axis, float x, float y, bool isLeftHand); void handlePoseEvent(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, const mat4& mat, const vec3& linearVelocity, const vec3& angularVelocity, bool isLeftHand); - void ViveControllerManager::InputDevice::partitionTouchpad(int sButton, int xAxis, int yAxis, int centerPsuedoButton, int outerPseudoButton); + void ViveControllerManager::InputDevice::partitionTouchpad(int sButton, int xAxis, int yAxis, int centerPsuedoButton, int xPseudoButton, int yPseudoButton); class FilteredStick { public: From 922468ce1c3777b9cd89fac03da8d94d7a86fa55 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Tue, 31 May 2016 20:47:58 -0700 Subject: [PATCH 03/93] fix cut and paste error with extraneous namespace qualifiers. --- plugins/openvr/src/ViveControllerManager.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/openvr/src/ViveControllerManager.h b/plugins/openvr/src/ViveControllerManager.h index e14a10a94e..f788e3f37c 100644 --- a/plugins/openvr/src/ViveControllerManager.h +++ b/plugins/openvr/src/ViveControllerManager.h @@ -61,7 +61,7 @@ private: void handleAxisEvent(float deltaTime, uint32_t axis, float x, float y, bool isLeftHand); void handlePoseEvent(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, const mat4& mat, const vec3& linearVelocity, const vec3& angularVelocity, bool isLeftHand); - void ViveControllerManager::InputDevice::partitionTouchpad(int sButton, int xAxis, int yAxis, int centerPsuedoButton, int xPseudoButton, int yPseudoButton); + void partitionTouchpad(int sButton, int xAxis, int yAxis, int centerPsuedoButton, int xPseudoButton, int yPseudoButton); class FilteredStick { public: From f0133013c267ae319c660e8083546c16880faf00 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Wed, 1 Jun 2016 07:30:26 -0700 Subject: [PATCH 04/93] typo --- plugins/openvr/src/ViveControllerManager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index f006c436e1..ba0ac88dfb 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -290,7 +290,7 @@ void ViveControllerManager::InputDevice::handleHandController(float deltaTime, u } } -void ViveControllerManager::InputDevice::partitionTouchpad(int sButton, int xAxis, int yAxis, int centerPseudoButton, int xPseudoButton, int yPesudoButton) { +void ViveControllerManager::InputDevice::partitionTouchpad(int sButton, int xAxis, int yAxis, int centerPseudoButton, int xPseudoButton, int yPseudoButton) { // Populate the L/RS_CENTER/OUTER pseudo buttons, corresponding to a partition of the L/RS space based on the X/Y values. const float CENTER_DEADBAND = 0.6f; const float DIAGONAL_DIVIDE_IN_RADIANS = PI / 4.0f; @@ -301,7 +301,7 @@ void ViveControllerManager::InputDevice::partitionTouchpad(int sButton, int xAxi float angle = glm::atan(cartesianQuadrantI.y / cartesianQuadrantI.x); float radius = glm::length(cartesianQuadrantI); bool isCenter = radius < CENTER_DEADBAND; - _buttonPressedMap.insert(isCenter ? centerPseudoButton : ((angle < DIAGONAL_DIVIDE_IN_RADIANS) ? xPseudoButton :yPesudoButton)); + _buttonPressedMap.insert(isCenter ? centerPseudoButton : ((angle < DIAGONAL_DIVIDE_IN_RADIANS) ? xPseudoButton :yPseudoButton)); } } From 84fa4402fd62772f247486b70018f75eb25f38c5 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 1 Jun 2016 14:03:49 -0700 Subject: [PATCH 05/93] Click on full trigger, activate on partial trigger. --- .../controllers/handControllerPointer.js | 94 +++++++++++++++---- 1 file changed, 78 insertions(+), 16 deletions(-) diff --git a/scripts/system/controllers/handControllerPointer.js b/scripts/system/controllers/handControllerPointer.js index ca3b5e8cf2..5cbde82654 100644 --- a/scripts/system/controllers/handControllerPointer.js +++ b/scripts/system/controllers/handControllerPointer.js @@ -47,22 +47,66 @@ function TimeLock(expiration) { } var handControllerLockOut = new TimeLock(2000); -// Calls onFunction() or offFunction() when swtich(on), but only if it is to a new value. -function LatchedToggle(onFunction, offFunction, state) { - this.getState = function () { - return state; +function Trigger() { + // This part is copied and adapted from handControllerGrab.js. Maybe we should refactor this. + var that = this; + that.TRIGGER_SMOOTH_RATIO = 0.1; // Time averaging of trigger - 0.0 disables smoothing + that.TRIGGER_ON_VALUE = 0.4; // Squeezed just enough to activate search or near grab + that.TRIGGER_GRAB_VALUE = 0.85; // Squeezed far enough to complete distant grab + that.TRIGGER_OFF_VALUE = 0.15; + that.rawTriggerValue = 0; + that.triggerValue = 0; // rolling average of trigger value + that.triggerPress = function (value) { + that.rawTriggerValue = value; }; - this.setState = function (on) { - if (state === on) { - return; - } - state = on; - if (on) { - onFunction(); - } else { - offFunction(); - } + that.updateSmoothedTrigger = function () { // e.g., call once/update for effect + var triggerValue = that.rawTriggerValue; + // smooth out trigger value + that.triggerValue = (that.triggerValue * that.TRIGGER_SMOOTH_RATIO) + + (triggerValue * (1.0 - that.TRIGGER_SMOOTH_RATIO)); }; + // Current smoothed state, without hysteresis. Answering booleans. + that.triggerSmoothedGrab = function () { + return that.triggerValue > that.TRIGGER_GRAB_VALUE; + }; + that.triggerSmoothedSqueezed = function () { + return that.triggerValue > that.TRIGGER_ON_VALUE; + }; + that.triggerSmoothedReleased = function () { + return that.triggerValue < that.TRIGGER_OFF_VALUE; + }; + + // This part is not from handControllerGrab.js + that.state = null; // tri-state: falsey, 'partial', 'full' + that.update = function () { // update state, called from an update function + var state = that.state; + that.updateSmoothedTrigger(); + + // The first two are independent of previous state: + if (that.triggerSmoothedGrab()) { + state = 'full'; + } else if (that.triggerSmoothedReleased()) { + state = null; + // These depend on previous state: + // null -> squeezed ==> partial + // full -> !squeezed ==> partial + // Otherwise no change. + } else if (that.triggerSmoothedSqueezed()) { + if (!state) { + state = 'partial'; + } + } else if (state === 'full') { + state = 'partial'; + } + that.state = state; + }; + // Answer a controller source function (answering either 0.0 or 1.0), with hysteresis. + that.partial = function () { + return that.state ? 1.0 : 0.0; // either 'partial' or 'full' + }; + that.full = function () { + return (that.state === 'full') ? 1.0 : 0.0; + } } // VERTICAL FIELD OF VIEW --------- @@ -257,28 +301,41 @@ setupHandler(Controller.mouseDoublePressEvent, onMouseClick); // CONTROLLER MAPPING --------- // +var leftTrigger = new Trigger(); +var rightTrigger = new Trigger(); +var activeTrigger = rightTrigger; var activeHand = Controller.Standard.RightHand; function toggleHand() { if (activeHand === Controller.Standard.RightHand) { activeHand = Controller.Standard.LeftHand; + activeTrigger = leftTrigger; } else { activeHand = Controller.Standard.RightHand; + activeTrigger = rightTrigger; } } var clickMapping = Controller.newMapping(Script.resolvePath('') + '-click'); Script.scriptEnding.connect(clickMapping.disable); +// Gather the trigger data for smoothing. +clickMapping.from(Controller.Standard.RT).peek().to(rightTrigger.triggerPress); +clickMapping.from(Controller.Standard.LT).peek().to(leftTrigger.triggerPress); +// The next two lines will be removed soon. Right now I want both trigger and button so we can compare. clickMapping.from(Controller.Standard.RightPrimaryThumb).peek().to(Controller.Actions.ReticleClick); clickMapping.from(Controller.Standard.LeftPrimaryThumb).peek().to(Controller.Actions.ReticleClick); +// Full somoothed trigger is a click. +clickMapping.from(rightTrigger.full).to(Controller.Actions.ReticleClick); +clickMapping.from(leftTrigger.full).to(Controller.Actions.ReticleClick); clickMapping.from(Controller.Standard.RightSecondaryThumb).peek().to(Controller.Actions.ContextMenu); clickMapping.from(Controller.Standard.LeftSecondaryThumb).peek().to(Controller.Actions.ContextMenu); -clickMapping.from(Controller.Standard.RightPrimaryThumb).peek().to(function (on) { +// Partial smoothed trigger is activation. +clickMapping.from(rightTrigger.partial).to(function (on) { if (on && (activeHand !== Controller.Standard.RightHand)) { toggleHand(); } }); -clickMapping.from(Controller.Standard.LeftPrimaryThumb).peek().to(function (on) { +clickMapping.from(leftTrigger.partial).to(function (on) { if (on && (activeHand !== Controller.Standard.LeftHand)) { toggleHand(); } @@ -359,6 +416,8 @@ function update() { if (!Window.hasFocus()) { // Don't mess with other apps return turnOffVisualization(); } + leftTrigger.update(); + rightTrigger.update(); var controllerPose = Controller.getPoseValue(activeHand); // Valid if any plugged-in hand controller is "on". (uncradled Hydra, green-lighted Vive...) if (!controllerPose.valid) { @@ -390,6 +449,9 @@ function update() { return turnOffVisualization(true); } // We are not pointing at a HUD element (but it could be a 3d overlay). + if (!activeTrigger.state) { + return turnOffVisualization(); // No trigger (with hysteresis). + } updateVisualization(controllerPosition, controllerDirection, hudPoint3d, hudPoint2d); } From e6cc1fabe0d97006024e38de4a498b1f364fa860 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Thu, 2 Jun 2016 12:38:54 -0700 Subject: [PATCH 06/93] Do not send stale vive data as valid after deactivating. --- plugins/openvr/src/OpenVrDisplayPlugin.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index dba8fca208..a74e5458af 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -96,6 +96,11 @@ void OpenVrDisplayPlugin::internalDeactivate() { Parent::internalDeactivate(); _container->setIsOptionChecked(StandingHMDSensorMode, false); if (_system) { + // Invalidate poses. It's fine if someone else sets these shared values, but we're about to stop updating them, and + // we don't want ViveControllerManager to consider old values to be valid. + for (int i = 0; i < vr::k_unMaxTrackedDeviceCount; i++) { + _trackedDevicePose[i].bPoseIsValid = false; + } releaseOpenVrSystem(); _system = nullptr; } From 8c13ff4ee15ba0ab7b2c8d8b9de3aef2fff0da66 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Tue, 31 May 2016 20:23:58 -0700 Subject: [PATCH 07/93] Pinned UI support --- interface/resources/qml/AddressBarDialog.qml | 9 +- interface/resources/qml/AssetServer.qml | 4 +- interface/resources/qml/Browser.qml | 4 +- interface/resources/qml/InfoView.qml | 2 +- interface/resources/qml/LoginDialog.qml | 11 +- interface/resources/qml/QmlWebWindow.qml | 4 +- interface/resources/qml/QmlWindow.qml | 2 +- interface/resources/qml/ToolWindow.qml | 10 +- .../qml/controls-uit/AttachmentsTable.qml | 2 +- interface/resources/qml/desktop/Desktop.qml | 74 ++- .../resources/qml/dialogs/FileDialog.qml | 4 +- .../resources/qml/dialogs/MessageDialog.qml | 4 +- .../qml/dialogs/PreferencesDialog.qml | 4 +- .../resources/qml/dialogs/QueryDialog.qml | 2 +- .../qml/dialogs/preferences/AvatarBrowser.qml | 2 +- .../qml/hifi/dialogs/AttachmentsDialog.qml | 4 +- .../qml/hifi/dialogs/ModelBrowserDialog.qml | 2 +- .../qml/hifi/dialogs/RunningScripts.qml | 7 +- .../hifi/dialogs/attachments/Attachment.qml | 2 +- .../qml/hifi/dialogs/attachments/Vector3.qml | 2 +- .../qml/windows-uit/DefaultFrame.qml | 119 ---- .../resources/qml/windows-uit/Fadable.qml | 60 -- interface/resources/qml/windows-uit/Frame.qml | 133 ----- .../resources/qml/windows-uit/ModalFrame.qml | 98 ---- .../resources/qml/windows-uit/ModalWindow.qml | 28 - .../resources/qml/windows-uit/Window.qml | 343 ------------ .../resources/qml/windows/DefaultFrame.qml | 118 ++-- interface/resources/qml/windows/Fadable.qml | 33 +- interface/resources/qml/windows/Frame.qml | 102 +++- .../resources/qml/windows/HiddenFrame.qml | 2 +- .../resources/qml/windows/ModalFrame.qml | 110 +++- .../resources/qml/windows/ModalWindow.qml | 24 +- interface/resources/qml/windows/Window.qml | 308 ++++++++-- interface/src/Application.cpp | 3 +- interface/src/ui/OverlayConductor.cpp | 61 +- interface/src/ui/OverlayConductor.h | 2 +- .../src/display-plugins/CompositorHelper.cpp | 56 +- .../src/display-plugins/CompositorHelper.h | 23 +- .../display-plugins/OpenGLDisplayPlugin.cpp | 49 +- .../display-plugins/hmd/HmdDisplayPlugin.cpp | 54 +- libraries/ui/src/ErrorDialog.cpp | 4 - libraries/ui/src/ErrorDialog.h | 1 - libraries/ui/src/OffscreenQmlDialog.cpp | 2 +- libraries/ui/src/OffscreenUi.cpp | 34 +- libraries/ui/src/OffscreenUi.h | 9 + libraries/ui/src/QmlWindowClass.cpp | 3 +- libraries/ui/src/Tooltip.cpp | 4 - libraries/ui/src/Tooltip.h | 2 - tests/ui/qml/main.qml | 529 +++++++++--------- tests/ui/qmlscratch.pro | 3 +- 50 files changed, 1003 insertions(+), 1469 deletions(-) delete mode 100644 interface/resources/qml/windows-uit/DefaultFrame.qml delete mode 100644 interface/resources/qml/windows-uit/Fadable.qml delete mode 100644 interface/resources/qml/windows-uit/Frame.qml delete mode 100644 interface/resources/qml/windows-uit/ModalFrame.qml delete mode 100644 interface/resources/qml/windows-uit/ModalWindow.qml delete mode 100644 interface/resources/qml/windows-uit/Window.qml diff --git a/interface/resources/qml/AddressBarDialog.qml b/interface/resources/qml/AddressBarDialog.qml index 7f107e44e9..a48804faba 100644 --- a/interface/resources/qml/AddressBarDialog.qml +++ b/interface/resources/qml/AddressBarDialog.qml @@ -20,9 +20,10 @@ Window { objectName: "AddressBarDialog" frame: HiddenFrame {} + hideBackground: true - visible: false - destroyOnInvisible: false + shown: false + destroyOnHidden: false resizable: false scale: 1.25 // Make this dialog a little larger than normal @@ -145,14 +146,14 @@ Window { if (addressLine.text !== "") { addressBarDialog.loadAddress(addressLine.text) } - root.visible = false; + root.shown = false; } Keys.onPressed: { switch (event.key) { case Qt.Key_Escape: case Qt.Key_Back: - root.visible = false + root.shown = false event.accepted = true break case Qt.Key_Enter: diff --git a/interface/resources/qml/AssetServer.qml b/interface/resources/qml/AssetServer.qml index 370bc92d81..e975037b5f 100644 --- a/interface/resources/qml/AssetServer.qml +++ b/interface/resources/qml/AssetServer.qml @@ -15,7 +15,7 @@ import Qt.labs.settings 1.0 import "styles-uit" import "controls-uit" as HifiControls -import "windows-uit" +import "windows" import "dialogs" Window { @@ -23,7 +23,7 @@ Window { objectName: "AssetServer" title: "Asset Browser" resizable: true - destroyOnInvisible: true + destroyOnHidden: true implicitWidth: 384; implicitHeight: 640 minSize: Qt.vector2d(200, 300) diff --git a/interface/resources/qml/Browser.qml b/interface/resources/qml/Browser.qml index 89ab333a0d..efa2063fe8 100644 --- a/interface/resources/qml/Browser.qml +++ b/interface/resources/qml/Browser.qml @@ -11,13 +11,13 @@ Window { HifiConstants { id: hifi } title: "Browser" resizable: true - destroyOnInvisible: true + destroyOnHidden: true width: 800 height: 600 property alias webView: webview Component.onCompleted: { - visible = true + shown = true addressBar.text = webview.url } diff --git a/interface/resources/qml/InfoView.qml b/interface/resources/qml/InfoView.qml index c5dba7e1f3..0f17a88614 100644 --- a/interface/resources/qml/InfoView.qml +++ b/interface/resources/qml/InfoView.qml @@ -12,7 +12,7 @@ import QtQuick 2.5 import Hifi 1.0 as Hifi import "controls-uit" -import "windows-uit" as Windows +import "windows" as Windows Windows.Window { id: root diff --git a/interface/resources/qml/LoginDialog.qml b/interface/resources/qml/LoginDialog.qml index 1b25b75608..f5030cb88d 100644 --- a/interface/resources/qml/LoginDialog.qml +++ b/interface/resources/qml/LoginDialog.qml @@ -22,8 +22,9 @@ Window { width: loginDialog.implicitWidth // FIXME make movable anchors.centerIn: parent - destroyOnInvisible: false - visible: false + destroyOnHidden: false + hideBackground: true + shown: false LoginDialog { id: loginDialog @@ -268,8 +269,8 @@ Window { } } - onVisibleChanged: { - if (!visible) { + onShownChanged: { + if (!shown) { username.text = "" password.text = "" loginDialog.statusText = "" @@ -282,7 +283,7 @@ Window { switch (event.key) { case Qt.Key_Escape: case Qt.Key_Back: - root.visible = false; + root.shown = false; event.accepted = true; break; diff --git a/interface/resources/qml/QmlWebWindow.qml b/interface/resources/qml/QmlWebWindow.qml index ae052879db..09c3bd7f28 100644 --- a/interface/resources/qml/QmlWebWindow.qml +++ b/interface/resources/qml/QmlWebWindow.qml @@ -13,7 +13,7 @@ import QtQuick.Controls 1.4 import QtWebEngine 1.1 import QtWebChannel 1.0 -import "windows-uit" as Windows +import "windows" as Windows import "controls-uit" as Controls import "styles-uit" @@ -22,7 +22,7 @@ Windows.Window { HifiConstants { id: hifi } title: "WebWindow" resizable: true - visible: false + shown: false // Don't destroy on close... otherwise the JS/C++ will have a dangling pointer destroyOnCloseButton: false property alias source: webview.url diff --git a/interface/resources/qml/QmlWindow.qml b/interface/resources/qml/QmlWindow.qml index 0420cd2e88..7be747a3ad 100644 --- a/interface/resources/qml/QmlWindow.qml +++ b/interface/resources/qml/QmlWindow.qml @@ -14,7 +14,7 @@ Windows.Window { HifiConstants { id: hifi } title: "QmlWindow" resizable: true - visible: false + shown: false focus: true property var channel; // Don't destroy on close... otherwise the JS/C++ will have a dangling pointer diff --git a/interface/resources/qml/ToolWindow.qml b/interface/resources/qml/ToolWindow.qml index aaff43b146..faa96fac5a 100644 --- a/interface/resources/qml/ToolWindow.qml +++ b/interface/resources/qml/ToolWindow.qml @@ -15,7 +15,7 @@ import QtWebEngine 1.1 import QtWebChannel 1.0 import Qt.labs.settings 1.0 -import "windows-uit" +import "windows" import "controls-uit" import "styles-uit" @@ -24,9 +24,9 @@ Window { resizable: true objectName: "ToolWindow" destroyOnCloseButton: false - destroyOnInvisible: false + destroyOnHidden: false closable: true - visible: false + shown: false title: "Edit" property alias tabView: tabView implicitWidth: 520; implicitHeight: 695 @@ -142,7 +142,7 @@ Window { return; } } - visible = false; + shown = false; } function findIndexForUrl(source) { @@ -172,7 +172,7 @@ Window { var tab = tabView.getTab(index); if (newVisible) { - toolWindow.visible = true + toolWindow.shown = true tab.enabled = true } else { tab.enabled = false; diff --git a/interface/resources/qml/controls-uit/AttachmentsTable.qml b/interface/resources/qml/controls-uit/AttachmentsTable.qml index ce93b8f4df..7d0280b72d 100644 --- a/interface/resources/qml/controls-uit/AttachmentsTable.qml +++ b/interface/resources/qml/controls-uit/AttachmentsTable.qml @@ -15,7 +15,7 @@ import QtQuick.XmlListModel 2.0 import "../styles-uit" import "../controls-uit" as HifiControls -import "../windows-uit" +import "../windows" import "../hifi/models" TableView { diff --git a/interface/resources/qml/desktop/Desktop.qml b/interface/resources/qml/desktop/Desktop.qml index 62a72e3d8c..73f8a17bb0 100644 --- a/interface/resources/qml/desktop/Desktop.qml +++ b/interface/resources/qml/desktop/Desktop.qml @@ -52,6 +52,7 @@ FocusScope { readonly property real menu: 8000 } + QtObject { id: d @@ -93,6 +94,17 @@ FocusScope { return item; } + function findMatchingChildren(item, predicate) { + var results = []; + for (var i in item.children) { + var child = item.children[i]; + if (predicate(child)) { + results.push(child); + } + } + return results; + } + function isTopLevelWindow(item) { return item.topLevelWindow; } @@ -106,19 +118,9 @@ FocusScope { } function getTopLevelWindows(predicate) { - var currentWindows = []; - if (!desktop) { - console.log("Could not find desktop for " + item) - return currentWindows; - } - - for (var i = 0; i < desktop.children.length; ++i) { - var child = desktop.children[i]; - if (isTopLevelWindow(child) && (!predicate || predicate(child))) { - currentWindows.push(child) - } - } - return currentWindows; + return findMatchingChildren(desktop, function(child) { + return (isTopLevelWindow(child) && (!predicate || predicate(child))); + }); } function getDesktopWindow(item) { @@ -227,19 +229,9 @@ FocusScope { } function getRepositionChildren(predicate) { - var currentWindows = []; - if (!desktop) { - console.log("Could not find desktop"); - return currentWindows; - } - - for (var i = 0; i < desktop.children.length; ++i) { - var child = desktop.children[i]; - if (child.shouldReposition === true && (!predicate || predicate(child))) { - currentWindows.push(child) - } - } - return currentWindows; + return findMatchingChildren(desktop, function(child) { + return (child.shouldReposition === true && (!predicate || predicate(child))); + }); } function repositionAll() { @@ -265,6 +257,35 @@ FocusScope { } } + property bool pinned: false + property var hiddenChildren: [] + + function togglePinned() { + pinned = !pinned + } + + onPinnedChanged: { + + if (pinned) { + hiddenChildren = d.findMatchingChildren(desktop, function(child){ + return !d.isTopLevelWindow(child) && child.visible; + }); + + hiddenChildren.forEach(function(child){ + child.visible = false; + }); + } else { + hiddenChildren.forEach(function(child){ + if (child) { + child.visible = true; + } + }); + hiddenChildren = []; + } + } + + onShowDesktop: pinned = false + function raise(item) { var targetWindow = d.getDesktopWindow(item); if (!targetWindow) { @@ -422,7 +443,6 @@ FocusScope { event.accepted = false; } - function unfocusWindows() { var windows = d.getTopLevelWindows(); for (var i = 0; i < windows.length; ++i) { diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index 015a192185..96676e986a 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -18,7 +18,7 @@ import QtQuick.Dialogs 1.2 as OriginalDialogs import ".." import "../controls-uit" import "../styles-uit" -import "../windows-uit" +import "../windows" import "fileDialog" @@ -724,7 +724,7 @@ ModalWindow { Action { id: cancelAction text: "Cancel" - onTriggered: { canceled(); root.visible = false; } + onTriggered: { canceled(); root.shown = false; } } } diff --git a/interface/resources/qml/dialogs/MessageDialog.qml b/interface/resources/qml/dialogs/MessageDialog.qml index 30f492e36a..d390ea08bf 100644 --- a/interface/resources/qml/dialogs/MessageDialog.qml +++ b/interface/resources/qml/dialogs/MessageDialog.qml @@ -14,7 +14,7 @@ import QtQuick.Dialogs 1.2 as OriginalDialogs import "../controls-uit" import "../styles-uit" -import "../windows-uit" +import "../windows" import "messageDialog" @@ -24,7 +24,7 @@ ModalWindow { implicitWidth: 640 implicitHeight: 320 destroyOnCloseButton: true - destroyOnInvisible: true + destroyOnHidden: true visible: true signal selected(int button); diff --git a/interface/resources/qml/dialogs/PreferencesDialog.qml b/interface/resources/qml/dialogs/PreferencesDialog.qml index 40cc713397..398e0abd8e 100644 --- a/interface/resources/qml/dialogs/PreferencesDialog.qml +++ b/interface/resources/qml/dialogs/PreferencesDialog.qml @@ -13,14 +13,14 @@ import QtQuick.Controls 1.4 import "../controls-uit" as HifiControls import "../styles-uit" -import "../windows-uit" +import "../windows" import "preferences" Window { id: root title: "Preferences" resizable: true - destroyOnInvisible: true + destroyOnHidden: true width: 500 height: 577 property var sections: [] diff --git a/interface/resources/qml/dialogs/QueryDialog.qml b/interface/resources/qml/dialogs/QueryDialog.qml index 0c7772dc94..05cb347169 100644 --- a/interface/resources/qml/dialogs/QueryDialog.qml +++ b/interface/resources/qml/dialogs/QueryDialog.qml @@ -14,7 +14,7 @@ import QtQuick.Dialogs 1.2 as OriginalDialogs import "../controls-uit" import "../styles-uit" -import "../windows-uit" +import "../windows" ModalWindow { id: root diff --git a/interface/resources/qml/dialogs/preferences/AvatarBrowser.qml b/interface/resources/qml/dialogs/preferences/AvatarBrowser.qml index e5bc9b80ef..90d2dc5284 100644 --- a/interface/resources/qml/dialogs/preferences/AvatarBrowser.qml +++ b/interface/resources/qml/dialogs/preferences/AvatarBrowser.qml @@ -12,7 +12,7 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 import QtWebEngine 1.1 -import "../../windows-uit" as Windows +import "../../windows" as Windows import "../../controls-uit" as Controls import "../../styles-uit" diff --git a/interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml b/interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml index 437e02e149..7b3a368bb0 100755 --- a/interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml +++ b/interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml @@ -6,7 +6,7 @@ import QtQuick.Controls.Styles 1.4 import "../../styles-uit" import "../../controls-uit" as HifiControls -import "../../windows-uit" +import "../../windows" import "attachments" Window { @@ -16,7 +16,7 @@ Window { width: 600 height: 600 resizable: true - destroyOnInvisible: true + destroyOnHidden: true minSize: Qt.vector2d(400, 500) HifiConstants { id: hifi } diff --git a/interface/resources/qml/hifi/dialogs/ModelBrowserDialog.qml b/interface/resources/qml/hifi/dialogs/ModelBrowserDialog.qml index b2de108545..29f0498f59 100644 --- a/interface/resources/qml/hifi/dialogs/ModelBrowserDialog.qml +++ b/interface/resources/qml/hifi/dialogs/ModelBrowserDialog.qml @@ -9,7 +9,7 @@ import "../models" import "../../styles-uit" import "../../controls-uit" as HifiControls -import "../../windows-uit" +import "../../windows" Window { id: root diff --git a/interface/resources/qml/hifi/dialogs/RunningScripts.qml b/interface/resources/qml/hifi/dialogs/RunningScripts.qml index 31bb553809..7dc7654d9a 100644 --- a/interface/resources/qml/hifi/dialogs/RunningScripts.qml +++ b/interface/resources/qml/hifi/dialogs/RunningScripts.qml @@ -15,14 +15,14 @@ import Qt.labs.settings 1.0 import "../../styles-uit" import "../../controls-uit" as HifiControls -import "../../windows-uit" +import "../../windows" Window { id: root objectName: "RunningScripts" title: "Running Scripts" resizable: true - destroyOnInvisible: true + destroyOnHidden: true implicitWidth: 400 implicitHeight: isHMD ? 695 : 728 minSize: Qt.vector2d(200, 300) @@ -34,6 +34,9 @@ Window { property var runningScriptsModel: ListModel { } property bool isHMD: false + onVisibleChanged: console.log("Running scripts visible changed to " + visible) + onShownChanged: console.log("Running scripts visible changed to " + visible) + Settings { category: "Overlay.RunningScripts" property alias x: root.x diff --git a/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml b/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml index 1277c459ce..04e3934535 100755 --- a/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml +++ b/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml @@ -8,7 +8,7 @@ import "." import ".." import "../../../styles-uit" import "../../../controls-uit" as HifiControls -import "../../../windows-uit" +import "../../../windows" Item { height: column.height + 2 * 8 diff --git a/interface/resources/qml/hifi/dialogs/attachments/Vector3.qml b/interface/resources/qml/hifi/dialogs/attachments/Vector3.qml index e1d7b6d4a3..3d109cc2a5 100644 --- a/interface/resources/qml/hifi/dialogs/attachments/Vector3.qml +++ b/interface/resources/qml/hifi/dialogs/attachments/Vector3.qml @@ -3,7 +3,7 @@ import QtQuick.Controls 1.4 import "../../../styles-uit" import "../../../controls-uit" as HifiControls -import "../../../windows-uit" +import "../../../windows" Item { id: root diff --git a/interface/resources/qml/windows-uit/DefaultFrame.qml b/interface/resources/qml/windows-uit/DefaultFrame.qml deleted file mode 100644 index 84f435480b..0000000000 --- a/interface/resources/qml/windows-uit/DefaultFrame.qml +++ /dev/null @@ -1,119 +0,0 @@ -// -// DefaultFrame.qml -// -// Created by Bradley Austin Davis on 12 Jan 2016 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -import QtQuick 2.5 -import QtGraphicalEffects 1.0 - -import "." -import "../styles-uit" - -Frame { - HifiConstants { id: hifi } - - Rectangle { - // Dialog frame - id: frameContent - - readonly property int iconSize: hifi.dimensions.frameIconSize - readonly property int frameMargin: 9 - readonly property int frameMarginLeft: frameMargin - readonly property int frameMarginRight: frameMargin - readonly property int frameMarginTop: 2 * frameMargin + iconSize - readonly property int frameMarginBottom: iconSize + 11 - - anchors { - topMargin: -frameMarginTop - leftMargin: -frameMarginLeft - rightMargin: -frameMarginRight - bottomMargin: -frameMarginBottom - } - anchors.fill: parent - color: hifi.colors.baseGrayHighlight40 - border { - width: hifi.dimensions.borderWidth - color: hifi.colors.faintGray50 - } - radius: hifi.dimensions.borderRadius - - // Enable dragging of the window - MouseArea { - anchors.fill: parent - drag.target: window - } - - Row { - id: controlsRow - anchors { - right: parent.right; - top: parent.top; - topMargin: frameContent.frameMargin + 1 // Move down a little to visually align with the title - rightMargin: frameContent.frameMarginRight; - } - spacing: frameContent.iconSize / 4 - - HiFiGlyphs { - // "Pin" button - visible: false - text: (frame.pinned && !pinClickArea.containsMouse) || (!frame.pinned && pinClickArea.containsMouse) ? hifi.glyphs.pinInverted : hifi.glyphs.pin - color: pinClickArea.containsMouse && !pinClickArea.pressed ? hifi.colors.redHighlight : hifi.colors.white - size: frameContent.iconSize - MouseArea { - id: pinClickArea - anchors.fill: parent - hoverEnabled: true - propagateComposedEvents: true - onClicked: { frame.pin(); mouse.accepted = false; } - } - } - - HiFiGlyphs { - // "Close" button - visible: window ? window.closable : false - text: closeClickArea.containsPress ? hifi.glyphs.closeInverted : hifi.glyphs.close - color: closeClickArea.containsMouse ? hifi.colors.redHighlight : hifi.colors.white - size: frameContent.iconSize - MouseArea { - id: closeClickArea - anchors.fill: parent - hoverEnabled: true - onClicked: window.visible = false; - } - } - } - - RalewayRegular { - // Title - id: titleText - anchors { - left: parent.left - leftMargin: frameContent.frameMarginLeft + hifi.dimensions.contentMargin.x - right: controlsRow.left - rightMargin: frameContent.iconSize - top: parent.top - topMargin: frameContent.frameMargin - } - text: window ? window.title : "" - color: hifi.colors.white - size: hifi.fontSizes.overlayTitle - } - - DropShadow { - source: titleText - anchors.fill: titleText - horizontalOffset: 2 - verticalOffset: 2 - samples: 2 - color: hifi.colors.baseGrayShadow60 - visible: (window && window.focus) - cached: true - } - } -} - diff --git a/interface/resources/qml/windows-uit/Fadable.qml b/interface/resources/qml/windows-uit/Fadable.qml deleted file mode 100644 index 34990c2147..0000000000 --- a/interface/resources/qml/windows-uit/Fadable.qml +++ /dev/null @@ -1,60 +0,0 @@ -// -// Fadable.qml -// -// Created by Bradley Austin Davis on 15 Jan 2016 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtGraphicalEffects 1.0 - -import "../styles-uit" - -// Enable window visibility transitions -FocusScope { - id: root - HifiConstants { id: hifi } - - Component.onCompleted: { - fadeTargetProperty = visible ? 1.0 : 0.0 - } - - // The target property to animate, usually scale or opacity - property alias fadeTargetProperty: root.opacity - // always start the property at 0 to enable fade in on creation - fadeTargetProperty: 0 - // DO NOT set visible to false or when derived types override it it - // will short circuit the fade in on initial visibility - // visible: false <--- NO - - // Some dialogs should be destroyed when they become - // invisible, so handle that - onVisibleChanged: { - // If someone directly set the visibility to false - // toggle it back on and use the targetVisible flag to transition - // via fading. - if ((!visible && fadeTargetProperty != 0.0) || (visible && fadeTargetProperty == 0.0)) { - var target = visible; - visible = !visible; - fadeTargetProperty = target ? 1.0 : 0.0; - return; - } - } - - // The actual animator - Behavior on fadeTargetProperty { - NumberAnimation { - duration: hifi.effects.fadeInDuration - easing.type: Easing.InOutCubic - } - } - - // Once we're transparent, disable the dialog's visibility - onFadeTargetPropertyChanged: { - visible = (fadeTargetProperty != 0.0); - } -} diff --git a/interface/resources/qml/windows-uit/Frame.qml b/interface/resources/qml/windows-uit/Frame.qml deleted file mode 100644 index 9519a44cf0..0000000000 --- a/interface/resources/qml/windows-uit/Frame.qml +++ /dev/null @@ -1,133 +0,0 @@ -// -// Frame.qml -// -// Created by Bradley Austin Davis on 12 Jan 2016 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -import QtQuick 2.5 -import QtGraphicalEffects 1.0 - -import "../styles-uit" -import "../js/Utils.js" as Utils - -Item { - id: frame - HifiConstants { id: hifi } - - default property var decoration - - property bool gradientsSupported: desktop.gradientsSupported - - readonly property int frameMarginLeft: frameContent.frameMarginLeft - readonly property int frameMarginRight: frameContent.frameMarginRight - readonly property int frameMarginTop: frameContent.frameMarginTop - readonly property int frameMarginBottom: frameContent.frameMarginBottom - - // Frames always fill their parents, but their decorations may extend - // beyond the window via negative margin sizes - anchors.fill: parent - - children: [ - focusShadow, - decoration, - sizeOutline, - debugZ, - sizeDrag - ] - - Text { - id: debugZ - visible: DebugQML - text: window ? "Z: " + window.z : "" - y: window ? window.height + 4 : 0 - } - - function deltaSize(dx, dy) { - var newSize = Qt.vector2d(window.width + dx, window.height + dy); - newSize = Utils.clampVector(newSize, window.minSize, window.maxSize); - window.width = newSize.x - window.height = newSize.y - } - - RadialGradient { - id: focusShadow - width: 1.66 * window.width - height: 1.66 * window.height - x: (window.width - width) / 2 - y: window.height / 2 - 0.375 * height - visible: gradientsSupported && window && window.focus && pane.visible - gradient: Gradient { - // GradientStop position 0.5 is at full circumference of circle that fits inside the square. - GradientStop { position: 0.0; color: "#ff000000" } // black, 100% opacity - GradientStop { position: 0.333; color: "#1f000000" } // black, 12% opacity - GradientStop { position: 0.5; color: "#00000000" } // black, 0% opacity - GradientStop { position: 1.0; color: "#00000000" } - } - cached: true - } - - Rectangle { - id: sizeOutline - x: -frameMarginLeft - y: -frameMarginTop - width: window ? window.width + frameMarginLeft + frameMarginRight + 2 : 0 - height: window ? window.height + frameMarginTop + frameMarginBottom + 2 : 0 - color: hifi.colors.baseGrayHighlight15 - border.width: 3 - border.color: hifi.colors.white50 - radius: hifi.dimensions.borderRadius - visible: window ? !pane.visible : false - } - - MouseArea { - // Resize handle - id: sizeDrag - width: hifi.dimensions.frameIconSize - height: hifi.dimensions.frameIconSize - enabled: window ? window.resizable : false - hoverEnabled: true - x: window ? window.width + frameMarginRight - hifi.dimensions.frameIconSize : 0 - y: window ? window.height + 4 : 0 - property vector2d pressOrigin - property vector2d sizeOrigin - property bool hid: false - onPressed: { - //console.log("Pressed on size") - pressOrigin = Qt.vector2d(mouseX, mouseY) - sizeOrigin = Qt.vector2d(window.content.width, window.content.height) - hid = false; - } - onReleased: { - if (hid) { - pane.visible = true - frameContent.visible = true - hid = false; - } - } - onPositionChanged: { - if (pressed) { - if (pane.visible) { - pane.visible = false; - frameContent.visible = false - hid = true; - } - var delta = Qt.vector2d(mouseX, mouseY).minus(pressOrigin); - frame.deltaSize(delta.x, delta.y) - } - } - HiFiGlyphs { - visible: sizeDrag.enabled - x: -11 // Move a little to visually align - y: window.modality == Qt.ApplicationModal ? -6 : -4 - text: hifi.glyphs.resizeHandle - size: hifi.dimensions.frameIconSize + 10 - color: sizeDrag.containsMouse || sizeDrag.pressed - ? hifi.colors.white - : (window.colorScheme == hifi.colorSchemes.dark ? hifi.colors.white50 : hifi.colors.lightGrayText80) - } - } -} diff --git a/interface/resources/qml/windows-uit/ModalFrame.qml b/interface/resources/qml/windows-uit/ModalFrame.qml deleted file mode 100644 index 211353b5f3..0000000000 --- a/interface/resources/qml/windows-uit/ModalFrame.qml +++ /dev/null @@ -1,98 +0,0 @@ -// -// ModalFrame.qml -// -// Created by Bradley Austin Davis on 15 Jan 2016 -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -import QtQuick 2.5 - -import "." -import "../controls-uit" -import "../styles-uit" - -Frame { - HifiConstants { id: hifi } - - Rectangle { - id: frameContent - - readonly property bool hasTitle: window.title != "" - - readonly property int frameMarginLeft: hifi.dimensions.modalDialogMargin.x - readonly property int frameMarginRight: hifi.dimensions.modalDialogMargin.x - readonly property int frameMarginTop: hifi.dimensions.modalDialogMargin.y + (frameContent.hasTitle ? hifi.dimensions.modalDialogTitleHeight + 10 : 0) - readonly property int frameMarginBottom: hifi.dimensions.modalDialogMargin.y - - signal frameClicked(); - - anchors { - fill: parent - topMargin: -frameMarginTop - leftMargin: -frameMarginLeft - rightMargin: -frameMarginRight - bottomMargin: -frameMarginBottom - } - - border { - width: hifi.dimensions.borderWidth - color: hifi.colors.lightGrayText80 - } - radius: hifi.dimensions.borderRadius - color: hifi.colors.faintGray - - // Enable dragging of the window - MouseArea { - anchors.fill: parent - drag.target: window - enabled: window.draggable - onClicked: window.frameClicked(); - } - - Item { - visible: frameContent.hasTitle - anchors.fill: parent - anchors { - topMargin: -parent.anchors.topMargin - leftMargin: -parent.anchors.leftMargin - rightMargin: -parent.anchors.rightMargin - } - - Item { - width: title.width + (icon.text !== "" ? icon.width + hifi.dimensions.contentSpacing.x : 0) - x: (parent.width - width) / 2 - - onWidthChanged: window.titleWidth = width - - HiFiGlyphs { - id: icon - text: window.iconText ? window.iconText : "" - size: window.iconSize ? window.iconSize : 30 - color: hifi.colors.lightGray - visible: text != "" - anchors.verticalCenter: title.verticalCenter - anchors.left: parent.left - } - RalewayRegular { - id: title - text: window.title - elide: Text.ElideRight - color: hifi.colors.baseGrayHighlight - size: hifi.fontSizes.overlayTitle - y: -hifi.dimensions.modalDialogTitleHeight - anchors.right: parent.right - } - } - - Rectangle { - anchors.left: parent.left - anchors.right: parent.right - height: 1 - color: hifi.colors.lightGray - } - } - } -} diff --git a/interface/resources/qml/windows-uit/ModalWindow.qml b/interface/resources/qml/windows-uit/ModalWindow.qml deleted file mode 100644 index 144165e4e1..0000000000 --- a/interface/resources/qml/windows-uit/ModalWindow.qml +++ /dev/null @@ -1,28 +0,0 @@ -// -// ModalWindow.qml -// -// Created by Bradley Austin Davis on 22 Jan 2016 -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -import QtQuick 2.5 - -import "." - -Window { - id: window - modality: Qt.ApplicationModal - destroyOnCloseButton: true - destroyOnInvisible: true - frame: ModalFrame { } - - property int colorScheme: hifi.colorSchemes.light - property bool draggable: false - - signal frameClicked(); - - anchors.centerIn: draggable ? undefined : parent -} diff --git a/interface/resources/qml/windows-uit/Window.qml b/interface/resources/qml/windows-uit/Window.qml deleted file mode 100644 index d614b21ce2..0000000000 --- a/interface/resources/qml/windows-uit/Window.qml +++ /dev/null @@ -1,343 +0,0 @@ -// -// Window.qml -// -// Created by Bradley Austin Davis on 12 Jan 2016 -// Copyright 2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 -import QtGraphicalEffects 1.0 - -import "." -import "../styles-uit" - -// FIXME how do I set the initial position of a window without -// overriding places where the a individual client of the window -// might be setting the position with a Settings{} element? - -// FIXME how to I enable dragging without allowing the window to lay outside -// of the desktop? How do I ensure when the desktop resizes all the windows -// are still at least partially visible? -Fadable { - id: window - HifiConstants { id: hifi } - - // The Window size is the size of the content, while the frame - // decorations can extend outside it. - implicitHeight: content ? content.height : 0 - implicitWidth: content ? content.width : 0 - x: desktop.invalid_position; y: desktop.invalid_position; - enabled: visible - - signal windowDestroyed(); - - property int modality: Qt.NonModal - readonly property bool topLevelWindow: true - property string title - // Should the window be closable control? - property bool closable: true - // Should the window try to remain on top of other windows? - property bool alwaysOnTop: false - // Should hitting the close button hide or destroy the window? - property bool destroyOnCloseButton: true - // Should hiding the window destroy it or just hide it? - property bool destroyOnInvisible: false - // FIXME support for pinned / unpinned pending full design - // property bool pinnable: false - // property bool pinned: false - property bool resizable: false - property bool gradientsSupported: desktop.gradientsSupported - property int colorScheme: hifi.colorSchemes.dark - - property vector2d minSize: Qt.vector2d(100, 100) - property vector2d maxSize: Qt.vector2d(1280, 800) - - // The content to place inside the window, determined by the client - default property var content - - property var footer: Item { } // Optional static footer at the bottom of the dialog. - - function setDefaultFocus() {} // Default function; can be overridden by dialogs. - - property var rectifier: Timer { - property bool executing: false; - interval: 100 - repeat: false - running: false - - onTriggered: { - executing = true; - x = Math.floor(x); - y = Math.floor(y); - executing = false; - } - - function begin() { - if (!executing) { - restart(); - } - } - } - onXChanged: rectifier.begin(); - onYChanged: rectifier.begin(); - - // This mouse area serves to raise the window. To function, it must live - // in the window and have a higher Z-order than the content, but follow - // the position and size of frame decoration - property var activator: MouseArea { - width: frame.decoration.width - height: frame.decoration.height - x: frame.decoration.anchors.leftMargin - y: frame.decoration.anchors.topMargin - propagateComposedEvents: true - acceptedButtons: Qt.AllButtons - enabled: window.visible - onPressed: { - //console.log("Pressed on activator area"); - window.raise(); - mouse.accepted = false; - } - } - - // This mouse area serves to swallow mouse events while the mouse is over the window - // to prevent things like mouse wheel events from reaching the application and changing - // the camera if the user is scrolling through a list and gets to the end. - property var swallower: MouseArea { - width: frame.decoration.width - height: frame.decoration.height - x: frame.decoration.anchors.leftMargin - y: frame.decoration.anchors.topMargin - hoverEnabled: true - acceptedButtons: Qt.AllButtons - enabled: window.visible - onClicked: {} - onDoubleClicked: {} - onPressAndHold: {} - onReleased: {} - onWheel: {} - } - - // Default to a standard frame. Can be overriden to provide custom - // frame styles, like a full desktop frame to simulate a modal window - property var frame: DefaultFrame { } - - // Scrollable window content. - property var pane: Item { - property bool isScrolling: scrollView.height < scrollView.contentItem.height - property int contentWidth: scrollView.width - (isScrolling ? 10 : 0) - property int scrollHeight: scrollView.height - - anchors.fill: parent - anchors.rightMargin: isScrolling ? 11 : 0 - - Rectangle { - id: contentBackground - anchors.fill: parent - anchors.rightMargin: parent.isScrolling ? 11 : 0 - color: hifi.colors.baseGray - visible: modality != Qt.ApplicationModal - } - - LinearGradient { - visible: gradientsSupported && modality != Qt.ApplicationModal - anchors.top: contentBackground.bottom - anchors.left: contentBackground.left - width: contentBackground.width - 1 - height: 4 - start: Qt.point(0, 0) - end: Qt.point(0, 4) - gradient: Gradient { - GradientStop { position: 0.0; color: hifi.colors.darkGray } - GradientStop { position: 1.0; color: hifi.colors.darkGray0 } - } - cached: true - } - - ScrollView { - id: scrollView - contentItem: content - horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff - verticalScrollBarPolicy: Qt.ScrollBarAsNeeded - anchors.fill: parent - anchors.rightMargin: parent.isScrolling ? 1 : 0 - anchors.bottomMargin: footer.height > 0 ? footerPane.height : 0 - - style: ScrollViewStyle { - - padding.right: -7 // Move to right away from content. - - handle: Item { - implicitWidth: 8 - Rectangle { - radius: 4 - color: hifi.colors.white30 - anchors { - fill: parent - leftMargin: 2 // Finesse size and position. - topMargin: 1 - bottomMargin: 1 - } - } - } - - scrollBarBackground: Item { - implicitWidth: 10 - Rectangle { - color: hifi.colors.darkGray30 - radius: 4 - anchors { - fill: parent - topMargin: -1 // Finesse size - bottomMargin: -2 - } - } - } - - incrementControl: Item { - visible: false - } - - decrementControl: Item { - visible: false - } - } - } - - Rectangle { - // Optional non-scrolling footer. - id: footerPane - anchors { - left: parent.left - bottom: parent.bottom - } - width: parent.contentWidth - height: footer.height + 2 * hifi.dimensions.contentSpacing.y + 3 - color: hifi.colors.baseGray - visible: footer.height > 0 - - Item { - // Horizontal rule. - anchors.fill: parent - - Rectangle { - width: parent.width - height: 1 - y: 1 // Stop displaying content just above horizontal rule/=. - color: hifi.colors.baseGrayShadow - } - - Rectangle { - width: parent.width - height: 1 - y: 2 - color: hifi.colors.baseGrayHighlight - } - } - - Item { - anchors.fill: parent - anchors.topMargin: 3 // Horizontal rule. - children: [ footer ] - } - } - } - - children: [ swallower, frame, pane, activator ] - - Component.onCompleted: { - window.parentChanged.connect(raise); - raise(); - setDefaultFocus(); - centerOrReposition(); - } - Component.onDestruction: { - window.parentChanged.disconnect(raise); // Prevent warning on shutdown - windowDestroyed(); - } - - onVisibleChanged: { - if (!visible && destroyOnInvisible) { - destroy(); - return; - } - if (visible) { - raise(); - } - enabled = visible - - if (visible && parent) { - centerOrReposition(); - } - } - - function centerOrReposition() { - if (x == desktop.invalid_position && y == desktop.invalid_position) { - desktop.centerOnVisible(window); - } else { - desktop.repositionOnVisible(window); - } - } - - function raise() { - if (visible && parent) { - desktop.raise(window) - } - } - - function pin() { -// pinned = ! pinned - } - - // our close function performs the same way as the OffscreenUI class: - // don't do anything but manipulate the targetVisible flag and let the other - // mechanisms decide if the window should be destroyed after the close - // animation completes - // FIXME using this close function messes up the visibility signals received by the - // type and it's derived types -// function close() { -// console.log("Closing " + window) -// if (destroyOnCloseButton) { -// destroyOnInvisible = true -// } -// visible = false; -// } - - function framedRect() { - if (!frame || !frame.decoration) { - return Qt.rect(0, 0, window.width, window.height) - } - return Qt.rect(frame.decoration.anchors.leftMargin, frame.decoration.anchors.topMargin, - window.width - frame.decoration.anchors.leftMargin - frame.decoration.anchors.rightMargin, - window.height - frame.decoration.anchors.topMargin - frame.decoration.anchors.bottomMargin) - } - - Keys.onPressed: { - switch(event.key) { - case Qt.Key_Control: - case Qt.Key_Shift: - case Qt.Key_Meta: - case Qt.Key_Alt: - break; - - case Qt.Key_W: - if (window.closable && (event.modifiers === Qt.ControlModifier)) { - visible = false - event.accepted = true - } - // fall through - - default: - // Consume unmodified keyboard entries while the window is focused, to prevent them - // from propagating to the application - if (event.modifiers === Qt.NoModifier) { - event.accepted = true; - } - break; - } - } -} diff --git a/interface/resources/qml/windows/DefaultFrame.qml b/interface/resources/qml/windows/DefaultFrame.qml index c58f9ca545..242209dbe0 100644 --- a/interface/resources/qml/windows/DefaultFrame.qml +++ b/interface/resources/qml/windows/DefaultFrame.qml @@ -1,20 +1,48 @@ +// +// DefaultFrame.qml +// +// Created by Bradley Austin Davis on 12 Jan 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + import QtQuick 2.5 +import QtGraphicalEffects 1.0 import "." -import "../controls" +import "../styles-uit" Frame { - id: frame - - property bool wideTopMargin: (window && (window.closable || window.title)); + HifiConstants { id: hifi } Rectangle { - anchors { margins: -iconSize; topMargin: -iconSize * (wideTopMargin ? 2 : 1); } - anchors.fill: parent; - color: "#7f7f7f7f"; - radius: 3; + // Dialog frame + id: frameContent - // Allow dragging of the window + readonly property int iconSize: hifi.dimensions.frameIconSize + readonly property int frameMargin: 9 + readonly property int frameMarginLeft: frameMargin + readonly property int frameMarginRight: frameMargin + readonly property int frameMarginTop: 2 * frameMargin + iconSize + readonly property int frameMarginBottom: iconSize + 11 + + anchors { + topMargin: -frameMarginTop + leftMargin: -frameMarginLeft + rightMargin: -frameMarginRight + bottomMargin: -frameMarginBottom + } + anchors.fill: parent + color: hifi.colors.baseGrayHighlight40 + border { + width: hifi.dimensions.borderWidth + color: hifi.colors.faintGray50 + } + radius: hifi.dimensions.borderRadius + + // Enable dragging of the window MouseArea { anchors.fill: parent drag.target: window @@ -22,48 +50,70 @@ Frame { Row { id: controlsRow - anchors { right: parent.right; top: parent.top; rightMargin: iconSize; topMargin: iconSize / 2; } - spacing: iconSize / 4 - FontAwesome { - visible: false - text: "\uf08d" - style: Text.Outline; styleColor: "white" - size: frame.iconSize - rotation: !frame.parent ? 90 : frame.parent.pinned ? 0 : 90 - color: frame.pinned ? "red" : "black" + anchors { + right: parent.right; + top: parent.top; + topMargin: frameContent.frameMargin + 1 // Move down a little to visually align with the title + rightMargin: frameContent.frameMarginRight; + } + spacing: frameContent.iconSize / 4 + + HiFiGlyphs { + // "Pin" button + visible: window.pinnable + text: window.pinned ? hifi.glyphs.pinInverted : hifi.glyphs.pin + color: pinClickArea.pressed ? hifi.colors.redHighlight : hifi.colors.white + size: frameContent.iconSize MouseArea { + id: pinClickArea anchors.fill: parent + hoverEnabled: true propagateComposedEvents: true - onClicked: { frame.pin(); mouse.accepted = false; } + onClicked: window.pinned = !window.pinned; } } - FontAwesome { + + HiFiGlyphs { + // "Close" button visible: window ? window.closable : false - text: closeClickArea.containsMouse ? "\uf057" : "\uf05c" - style: Text.Outline; - styleColor: "white" - color: closeClickArea.containsMouse ? "red" : "black" - size: frame.iconSize + text: closeClickArea.containsPress ? hifi.glyphs.closeInverted : hifi.glyphs.close + color: closeClickArea.containsMouse ? hifi.colors.redHighlight : hifi.colors.white + size: frameContent.iconSize MouseArea { id: closeClickArea anchors.fill: parent hoverEnabled: true - onClicked: window.visible = false; + onClicked: window.shown = false; } } } - Text { + RalewayRegular { + // Title id: titleText - anchors { left: parent.left; leftMargin: iconSize; right: controlsRow.left; rightMargin: iconSize; top: parent.top; topMargin: iconSize / 2; } + anchors { + left: parent.left + leftMargin: frameContent.frameMarginLeft + hifi.dimensions.contentMargin.x + right: controlsRow.left + rightMargin: frameContent.iconSize + top: parent.top + topMargin: frameContent.frameMargin + } text: window ? window.title : "" - elide: Text.ElideRight - font.bold: true - color: (window && window.focus) ? "white" : "gray" - style: Text.Outline; - styleColor: "black" + color: hifi.colors.white + size: hifi.fontSizes.overlayTitle + } + + DropShadow { + source: titleText + anchors.fill: titleText + horizontalOffset: 2 + verticalOffset: 2 + samples: 2 + color: hifi.colors.baseGrayShadow60 + visible: (window && window.focus) + cached: true } } - } diff --git a/interface/resources/qml/windows/Fadable.qml b/interface/resources/qml/windows/Fadable.qml index 0352966bd0..38cd4bf1f9 100644 --- a/interface/resources/qml/windows/Fadable.qml +++ b/interface/resources/qml/windows/Fadable.qml @@ -1,8 +1,18 @@ +// +// Fadable.qml +// +// Created by Bradley Austin Davis on 15 Jan 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + import QtQuick 2.5 import QtQuick.Controls 1.4 import QtGraphicalEffects 1.0 -import "." -import "../styles" + +import "../styles-uit" // Enable window visibility transitions FocusScope { @@ -13,6 +23,7 @@ FocusScope { fadeTargetProperty = visible ? 1.0 : 0.0 } + property var completionCallback; // The target property to animate, usually scale or opacity property alias fadeTargetProperty: root.opacity // always start the property at 0 to enable fade in on creation @@ -33,6 +44,13 @@ FocusScope { fadeTargetProperty = target ? 1.0 : 0.0; return; } + + // Now handle completions + if (completionCallback) { + completionCallback(); + completionCallback = undefined; + } + } // The actual animator @@ -43,8 +61,17 @@ FocusScope { } } - // Once we're transparent, disable the dialog's visibility onFadeTargetPropertyChanged: { visible = (fadeTargetProperty != 0.0); } + + function fadeIn(callback) { + completionCallback = callback; + fadeTargetProperty = 1.0; + } + + function fadeOut(callback) { + completionCallback = callback; + fadeTargetProperty = 0.0; + } } diff --git a/interface/resources/qml/windows/Frame.qml b/interface/resources/qml/windows/Frame.qml index 20bf669b9a..9519a44cf0 100644 --- a/interface/resources/qml/windows/Frame.qml +++ b/interface/resources/qml/windows/Frame.qml @@ -1,24 +1,42 @@ -import QtQuick 2.5 +// +// Frame.qml +// +// Created by Bradley Austin Davis on 12 Jan 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// -import "../controls" +import QtQuick 2.5 +import QtGraphicalEffects 1.0 + +import "../styles-uit" import "../js/Utils.js" as Utils Item { id: frame + HifiConstants { id: hifi } + + default property var decoration + + property bool gradientsSupported: desktop.gradientsSupported + + readonly property int frameMarginLeft: frameContent.frameMarginLeft + readonly property int frameMarginRight: frameContent.frameMarginRight + readonly property int frameMarginTop: frameContent.frameMarginTop + readonly property int frameMarginBottom: frameContent.frameMarginBottom + // Frames always fill their parents, but their decorations may extend // beyond the window via negative margin sizes anchors.fill: parent - // Convenience accessor for the window - property alias window: frame.parent - readonly property int iconSize: 24 - default property var decoration; - children: [ + focusShadow, decoration, sizeOutline, debugZ, - sizeDrag, + sizeDrag ] Text { @@ -35,57 +53,81 @@ Item { window.height = newSize.y } + RadialGradient { + id: focusShadow + width: 1.66 * window.width + height: 1.66 * window.height + x: (window.width - width) / 2 + y: window.height / 2 - 0.375 * height + visible: gradientsSupported && window && window.focus && pane.visible + gradient: Gradient { + // GradientStop position 0.5 is at full circumference of circle that fits inside the square. + GradientStop { position: 0.0; color: "#ff000000" } // black, 100% opacity + GradientStop { position: 0.333; color: "#1f000000" } // black, 12% opacity + GradientStop { position: 0.5; color: "#00000000" } // black, 0% opacity + GradientStop { position: 1.0; color: "#00000000" } + } + cached: true + } + Rectangle { id: sizeOutline - width: window ? window.width : 0 - height: window ? window.height : 0 - color: "#00000000" - border.width: 4 - radius: 10 - visible: window ? !window.content.visible : false + x: -frameMarginLeft + y: -frameMarginTop + width: window ? window.width + frameMarginLeft + frameMarginRight + 2 : 0 + height: window ? window.height + frameMarginTop + frameMarginBottom + 2 : 0 + color: hifi.colors.baseGrayHighlight15 + border.width: 3 + border.color: hifi.colors.white50 + radius: hifi.dimensions.borderRadius + visible: window ? !pane.visible : false } MouseArea { + // Resize handle id: sizeDrag - width: iconSize - height: iconSize + width: hifi.dimensions.frameIconSize + height: hifi.dimensions.frameIconSize enabled: window ? window.resizable : false - x: window ? window.width : 0 - y: window ? window.height : 0 + hoverEnabled: true + x: window ? window.width + frameMarginRight - hifi.dimensions.frameIconSize : 0 + y: window ? window.height + 4 : 0 property vector2d pressOrigin property vector2d sizeOrigin property bool hid: false onPressed: { - console.log("Pressed on size") + //console.log("Pressed on size") pressOrigin = Qt.vector2d(mouseX, mouseY) sizeOrigin = Qt.vector2d(window.content.width, window.content.height) hid = false; } onReleased: { if (hid) { - window.content.visible = true + pane.visible = true + frameContent.visible = true hid = false; } } onPositionChanged: { if (pressed) { - if (window.content.visible) { - window.content.visible = false; + if (pane.visible) { + pane.visible = false; + frameContent.visible = false hid = true; } var delta = Qt.vector2d(mouseX, mouseY).minus(pressOrigin); frame.deltaSize(delta.x, delta.y) } } - FontAwesome { + HiFiGlyphs { visible: sizeDrag.enabled - rotation: -45 - anchors { centerIn: parent } - horizontalAlignment: Text.AlignHCenter - text: "\uf07d" - size: iconSize / 3 * 2 - style: Text.Outline; styleColor: "white" + x: -11 // Move a little to visually align + y: window.modality == Qt.ApplicationModal ? -6 : -4 + text: hifi.glyphs.resizeHandle + size: hifi.dimensions.frameIconSize + 10 + color: sizeDrag.containsMouse || sizeDrag.pressed + ? hifi.colors.white + : (window.colorScheme == hifi.colorSchemes.dark ? hifi.colors.white50 : hifi.colors.lightGrayText80) } } - } diff --git a/interface/resources/qml/windows/HiddenFrame.qml b/interface/resources/qml/windows/HiddenFrame.qml index 2621b71eed..3d3fd047e2 100644 --- a/interface/resources/qml/windows/HiddenFrame.qml +++ b/interface/resources/qml/windows/HiddenFrame.qml @@ -2,7 +2,7 @@ import QtQuick 2.5 import "." -Frame { +Item { id: frame Item { anchors.fill: parent } diff --git a/interface/resources/qml/windows/ModalFrame.qml b/interface/resources/qml/windows/ModalFrame.qml index eb4641bc75..211353b5f3 100644 --- a/interface/resources/qml/windows/ModalFrame.qml +++ b/interface/resources/qml/windows/ModalFrame.qml @@ -1,36 +1,98 @@ +// +// ModalFrame.qml +// +// Created by Bradley Austin Davis on 15 Jan 2016 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + import QtQuick 2.5 import "." -import "../controls" +import "../controls-uit" +import "../styles-uit" Frame { - id: frame + HifiConstants { id: hifi } - Item { - anchors.fill: parent + Rectangle { + id: frameContent - Rectangle { - id: background - anchors.fill: parent - anchors.margins: -4096 - visible: window.visible - color: "#7f7f7f7f"; - radius: 3; + readonly property bool hasTitle: window.title != "" + + readonly property int frameMarginLeft: hifi.dimensions.modalDialogMargin.x + readonly property int frameMarginRight: hifi.dimensions.modalDialogMargin.x + readonly property int frameMarginTop: hifi.dimensions.modalDialogMargin.y + (frameContent.hasTitle ? hifi.dimensions.modalDialogTitleHeight + 10 : 0) + readonly property int frameMarginBottom: hifi.dimensions.modalDialogMargin.y + + signal frameClicked(); + + anchors { + fill: parent + topMargin: -frameMarginTop + leftMargin: -frameMarginLeft + rightMargin: -frameMarginRight + bottomMargin: -frameMarginBottom } - Text { - y: -implicitHeight - iconSize / 2 - text: window.title - elide: Text.ElideRight - font.bold: true - color: window.focus ? "white" : "gray" - style: Text.Outline; - styleColor: "black" + border { + width: hifi.dimensions.borderWidth + color: hifi.colors.lightGrayText80 + } + radius: hifi.dimensions.borderRadius + color: hifi.colors.faintGray + + // Enable dragging of the window + MouseArea { + anchors.fill: parent + drag.target: window + enabled: window.draggable + onClicked: window.frameClicked(); + } + + Item { + visible: frameContent.hasTitle + anchors.fill: parent + anchors { + topMargin: -parent.anchors.topMargin + leftMargin: -parent.anchors.leftMargin + rightMargin: -parent.anchors.rightMargin + } + + Item { + width: title.width + (icon.text !== "" ? icon.width + hifi.dimensions.contentSpacing.x : 0) + x: (parent.width - width) / 2 + + onWidthChanged: window.titleWidth = width + + HiFiGlyphs { + id: icon + text: window.iconText ? window.iconText : "" + size: window.iconSize ? window.iconSize : 30 + color: hifi.colors.lightGray + visible: text != "" + anchors.verticalCenter: title.verticalCenter + anchors.left: parent.left + } + RalewayRegular { + id: title + text: window.title + elide: Text.ElideRight + color: hifi.colors.baseGrayHighlight + size: hifi.fontSizes.overlayTitle + y: -hifi.dimensions.modalDialogTitleHeight + anchors.right: parent.right + } + } + + Rectangle { + anchors.left: parent.left + anchors.right: parent.right + height: 1 + color: hifi.colors.lightGray + } } } - - - - } - diff --git a/interface/resources/qml/windows/ModalWindow.qml b/interface/resources/qml/windows/ModalWindow.qml index 32443e70e3..144165e4e1 100644 --- a/interface/resources/qml/windows/ModalWindow.qml +++ b/interface/resources/qml/windows/ModalWindow.qml @@ -1,14 +1,28 @@ +// +// ModalWindow.qml +// +// Created by Bradley Austin Davis on 22 Jan 2016 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + import QtQuick 2.5 import "." Window { - id: root - anchors.centerIn: parent + id: window modality: Qt.ApplicationModal destroyOnCloseButton: true destroyOnInvisible: true - frame: ModalFrame{} + frame: ModalFrame { } + + property int colorScheme: hifi.colorSchemes.light + property bool draggable: false + + signal frameClicked(); + + anchors.centerIn: draggable ? undefined : parent } - - diff --git a/interface/resources/qml/windows/Window.qml b/interface/resources/qml/windows/Window.qml index 3abdbacc64..ed1b820fc8 100644 --- a/interface/resources/qml/windows/Window.qml +++ b/interface/resources/qml/windows/Window.qml @@ -1,9 +1,20 @@ +// +// Window.qml +// +// Created by Bradley Austin Davis on 12 Jan 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + import QtQuick 2.5 import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 import QtGraphicalEffects 1.0 import "." -import "../styles" +import "../styles-uit" // FIXME how do I set the initial position of a window without // overriding places where the a individual client of the window @@ -15,16 +26,36 @@ import "../styles" Fadable { id: window HifiConstants { id: hifi } + + // + // Signals + // + signal windowDestroyed(); + + // + // Native properties + // + // The Window size is the size of the content, while the frame // decorations can extend outside it. implicitHeight: content ? content.height : 0 implicitWidth: content ? content.width : 0 x: desktop.invalid_position; y: desktop.invalid_position; - enabled: visible + children: [ swallower, frame, pane, activator ] - signal windowDestroyed(); + // + // Custom properties + // property int modality: Qt.NonModal + // Corresponds to the window shown / hidden state AS DISTINCT from window visibility. + // Window visibility should NOT be used as a proxy for any other behavior. + property bool shown: true + // FIXME workaround to deal with the face that some visual items are defined here, + // when they should be moved to a frame derived type + property bool hideBackground: false + visible: shown + enabled: visible readonly property bool topLevelWindow: true property string title // Should the window be closable control? @@ -34,17 +65,23 @@ Fadable { // Should hitting the close button hide or destroy the window? property bool destroyOnCloseButton: true // Should hiding the window destroy it or just hide it? - property bool destroyOnInvisible: false - // FIXME support for pinned / unpinned pending full design - // property bool pinnable: false - // property bool pinned: false + property bool destroyOnHidden: false + property bool pinnable: true + property bool pinned: false property bool resizable: false + property bool gradientsSupported: desktop.gradientsSupported + property int colorScheme: hifi.colorSchemes.dark + property vector2d minSize: Qt.vector2d(100, 100) - property vector2d maxSize: Qt.vector2d(1280, 720) + property vector2d maxSize: Qt.vector2d(1280, 800) // The content to place inside the window, determined by the client default property var content + property var footer: Item { } // Optional static footer at the bottom of the dialog. + + function setDefaultFocus() {} // Default function; can be overridden by dialogs. + property var rectifier: Timer { property bool executing: false; interval: 100 @@ -65,20 +102,15 @@ Fadable { } } - - onXChanged: rectifier.begin(); - onYChanged: rectifier.begin(); - // This mouse area serves to raise the window. To function, it must live // in the window and have a higher Z-order than the content, but follow // the position and size of frame decoration property var activator: MouseArea { width: frame.decoration.width height: frame.decoration.height - x: frame.decoration.anchors.margins + x: frame.decoration.anchors.leftMargin y: frame.decoration.anchors.topMargin propagateComposedEvents: true - hoverEnabled: true acceptedButtons: Qt.AllButtons enabled: window.visible onPressed: { @@ -94,7 +126,7 @@ Fadable { property var swallower: MouseArea { width: frame.decoration.width height: frame.decoration.height - x: frame.decoration.anchors.margins + x: frame.decoration.anchors.leftMargin y: frame.decoration.anchors.topMargin hoverEnabled: true acceptedButtons: Qt.AllButtons @@ -106,71 +138,241 @@ Fadable { onWheel: {} } - // Default to a standard frame. Can be overriden to provide custom // frame styles, like a full desktop frame to simulate a modal window property var frame: DefaultFrame { } + // Scrollable window content. + // FIXME this should not define any visual content in this type. The base window + // type should only consist of logic sized areas, with nothing drawn (although the + // default value for the frame property does include visual decorations) + property var pane: Item { + property bool isScrolling: scrollView.height < scrollView.contentItem.height + property int contentWidth: scrollView.width - (isScrolling ? 10 : 0) + property int scrollHeight: scrollView.height - children: [ swallower, frame, content, activator ] + anchors.fill: parent + anchors.rightMargin: isScrolling ? 11 : 0 + Rectangle { + id: contentBackground + anchors.fill: parent + anchors.rightMargin: parent.isScrolling ? 11 : 0 + color: hifi.colors.baseGray + visible: !window.hideBackground && modality != Qt.ApplicationModal + } + + + LinearGradient { + visible: !window.hideBackground && gradientsSupported && modality != Qt.ApplicationModal + anchors.top: contentBackground.bottom + anchors.left: contentBackground.left + width: contentBackground.width - 1 + height: 4 + start: Qt.point(0, 0) + end: Qt.point(0, 4) + gradient: Gradient { + GradientStop { position: 0.0; color: hifi.colors.darkGray } + GradientStop { position: 1.0; color: hifi.colors.darkGray0 } + } + cached: true + } + + ScrollView { + id: scrollView + contentItem: content + horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff + verticalScrollBarPolicy: Qt.ScrollBarAsNeeded + anchors.fill: parent + anchors.rightMargin: parent.isScrolling ? 1 : 0 + anchors.bottomMargin: footer.height > 0 ? footerPane.height : 0 + + style: ScrollViewStyle { + + padding.right: -7 // Move to right away from content. + + handle: Item { + implicitWidth: 8 + Rectangle { + radius: 4 + color: hifi.colors.white30 + anchors { + fill: parent + leftMargin: 2 // Finesse size and position. + topMargin: 1 + bottomMargin: 1 + } + } + } + + scrollBarBackground: Item { + implicitWidth: 10 + Rectangle { + color: hifi.colors.darkGray30 + radius: 4 + anchors { + fill: parent + topMargin: -1 // Finesse size + bottomMargin: -2 + } + } + } + + incrementControl: Item { + visible: false + } + + decrementControl: Item { + visible: false + } + } + } + + Rectangle { + // Optional non-scrolling footer. + id: footerPane + anchors { + left: parent.left + bottom: parent.bottom + } + width: parent.contentWidth + height: footer.height + 2 * hifi.dimensions.contentSpacing.y + 3 + color: hifi.colors.baseGray + visible: footer.height > 0 + + Item { + // Horizontal rule. + anchors.fill: parent + + Rectangle { + width: parent.width + height: 1 + y: 1 // Stop displaying content just above horizontal rule/=. + color: hifi.colors.baseGrayShadow + } + + Rectangle { + width: parent.width + height: 1 + y: 2 + color: hifi.colors.baseGrayHighlight + } + } + + Item { + anchors.fill: parent + anchors.topMargin: 3 // Horizontal rule. + children: [ footer ] + } + } + } + + // + // Handlers + // Component.onCompleted: { window.parentChanged.connect(raise); - raise(); - centerOrReposition(); + setDefaultFocus(); + d.centerOrReposition(); + d.updateVisibility(shown); } Component.onDestruction: { window.parentChanged.disconnect(raise); // Prevent warning on shutdown windowDestroyed(); } - function centerOrReposition() { - if (x == desktop.invalid_position && y == desktop.invalid_position) { - desktop.centerOnVisible(window); - } else { - desktop.repositionOnVisible(window); - } - } + onXChanged: rectifier.begin(); + onYChanged: rectifier.begin(); + + onShownChanged: d.updateVisibility(shown) onVisibleChanged: { - if (!visible && destroyOnInvisible) { - destroy(); - return; - } - if (visible) { - raise(); - } enabled = visible - if (visible && parent) { - centerOrReposition(); + d.centerOrReposition(); } } + QtObject { + id: d + + readonly property alias pinned: window.pinned + readonly property alias shown: window.shown + readonly property alias modality: window.modality; + + function getTargetVisibility() { + if (!window.shown) { + return false; + } + + if (modality !== Qt.NonModal) { + return true; + } + + if (pinned) { + return true; + } + + if (desktop && !desktop.pinned) { + return true; + } + + return false; + } + + // The force flag causes all windows to fade back in, because a window was shown + readonly property alias visible: window.visible + function updateVisibility(force) { + if (force && !pinned && desktop.pinned) { + // Change the pinned state (which in turn will call us again) + desktop.pinned = false; + return; + } + + var targetVisibility = getTargetVisibility(); + if (targetVisibility === visible) { + return; + } + + if (targetVisibility) { + fadeIn(function() { + if (force) { + window.raise(); + } + }); + } else { + fadeOut(function() { + if (!window.shown && window.destroyOnHidden) { + window.destroy(); + } + }); + } + } + + function centerOrReposition() { + if (x == desktop.invalid_position && y == desktop.invalid_position) { + desktop.centerOnVisible(window); + } else { + desktop.repositionOnVisible(window); + } + } + + } + + // When the desktop pinned state changes, automatically handle the current windows + Connections { target: desktop; onPinnedChanged: d.updateVisibility() } + + function raise() { if (visible && parent) { desktop.raise(window) } } - function pin() { -// pinned = ! pinned + function setPinned() { + pinned = !pinned } - // our close function performs the same way as the OffscreenUI class: - // don't do anything but manipulate the targetVisible flag and let the other - // mechanisms decide if the window should be destroyed after the close - // animation completes - // FIXME using this close function messes up the visibility signals received by the - // type and it's derived types -// function close() { -// console.log("Closing " + window) -// if (destroyOnCloseButton) { -// destroyOnInvisible = true -// } -// visible = false; -// } - function framedRect() { if (!frame || !frame.decoration) { return Qt.rect(0, 0, window.width, window.height) @@ -180,7 +382,6 @@ Fadable { window.height - frame.decoration.anchors.topMargin - frame.decoration.anchors.bottomMargin) } - Keys.onPressed: { switch(event.key) { case Qt.Key_Control: @@ -189,10 +390,9 @@ Fadable { case Qt.Key_Alt: break; - case Qt.Key_W: if (window.closable && (event.modifiers === Qt.ControlModifier)) { - visible = false + shown = false event.accepted = true } // fall through diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 48b418b93c..c63973985a 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2057,7 +2057,8 @@ void Application::keyPressEvent(QKeyEvent* event) { case Qt::Key_X: if (isShifted && isMeta) { auto offscreenUi = DependencyManager::get(); - offscreenUi->getRootContext()->engine()->clearComponentCache(); + offscreenUi->togglePinned(); + //offscreenUi->getRootContext()->engine()->clearComponentCache(); //OffscreenUi::information("Debugging", "Component cache cleared"); // placeholder for dialogs being converted to QML. } diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index 83d729779c..54dba229e3 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -96,7 +96,7 @@ void OverlayConductor::updateMode() { myAvatar->reset(true, false, false); } if (_wantsOverlays) { - setEnabled(!nowDriving, false); + setEnabled(!nowDriving); } _driving = nowDriving; } // Else haven't accumulated enough time in new mode, but keep timing. @@ -114,18 +114,14 @@ void OverlayConductor::updateMode() { case SITTING: { // enter the SITTING state // place the overlay at origin - Transform identity; - qApp->getApplicationCompositor().setModelTransform(identity); + qApp->getApplicationCompositor().setModelTransform(Transform()); break; } case STANDING: { // STANDING mode is not currently used. // enter the STANDING state // place the overlay at the current hmd position in world space auto camMat = cancelOutRollAndPitch(myAvatar->getSensorToWorldMatrix() * qApp->getHMDSensorPose()); - Transform t; - t.setTranslation(extractTranslation(camMat)); - t.setRotation(glm::quat_cast(camMat)); - qApp->getApplicationCompositor().setModelTransform(t); + qApp->getApplicationCompositor().setModelTransform(Transform(camMat)); break; } @@ -139,54 +135,21 @@ void OverlayConductor::updateMode() { } -void OverlayConductor::setEnabled(bool enabled, bool toggleQmlEvents) { - +void OverlayConductor::setEnabled(bool enabled) { if (enabled == _enabled) { return; } - if (toggleQmlEvents) { // Could recurse on us with the wrong toggleQmlEvents flag, and not need in the !toggleQmlEvent case anyway. - Menu::getInstance()->setIsOptionChecked(MenuOption::Overlays, enabled); - } - _enabled = enabled; // set the new value - + auto offscreenUi = DependencyManager::get(); + offscreenUi->setPinned(!_enabled); // if the new state is visible/enabled... - if (_enabled) { - // alpha fadeIn the overlay mesh. - qApp->getApplicationCompositor().fadeIn(); - - // enable mouse clicks from script - qApp->getOverlays().enable(); - - // enable QML events - if (toggleQmlEvents) { - auto offscreenUi = DependencyManager::get(); - offscreenUi->getRootItem()->setEnabled(true); - } - - if (_mode == STANDING) { - // place the overlay at the current hmd position in world space - MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); - auto camMat = cancelOutRollAndPitch(myAvatar->getSensorToWorldMatrix() * qApp->getHMDSensorPose()); - Transform t; - t.setTranslation(extractTranslation(camMat)); - t.setRotation(glm::quat_cast(camMat)); - qApp->getApplicationCompositor().setModelTransform(t); - } - } else { // other wise, if the new state is hidden/not enabled - // alpha fadeOut the overlay mesh. - qApp->getApplicationCompositor().fadeOut(); - - // disable mouse clicks from script - qApp->getOverlays().disable(); - - // disable QML events - if (toggleQmlEvents) { // I'd really rather always do this, but it looses drive state. bugzid:501 - auto offscreenUi = DependencyManager::get(); - offscreenUi->getRootItem()->setEnabled(false); - } - } + if (_enabled && _mode == STANDING) { + // place the overlay at the current hmd position in world space + MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); + auto camMat = cancelOutRollAndPitch(myAvatar->getSensorToWorldMatrix() * qApp->getHMDSensorPose()); + qApp->getApplicationCompositor().setModelTransform(Transform(camMat)); + } } bool OverlayConductor::getEnabled() const { diff --git a/interface/src/ui/OverlayConductor.h b/interface/src/ui/OverlayConductor.h index 99f4b56584..1ec66663a4 100644 --- a/interface/src/ui/OverlayConductor.h +++ b/interface/src/ui/OverlayConductor.h @@ -17,7 +17,7 @@ public: ~OverlayConductor(); void update(float dt); - void setEnabled(bool enable, bool toggleQmlEvents = true); + void setEnabled(bool enable); bool getEnabled() const; private: diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp index f9d527de8f..d4fff1b976 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp @@ -346,7 +346,7 @@ bool CompositorHelper::calculateRayUICollisionPoint(const glm::vec3& position, c auto relativePosition = vec3(relativePosition4) / relativePosition4.w; auto relativeDirection = glm::inverse(glm::quat_cast(UITransform)) * direction; - float uiRadius = _oculusUIRadius; // * myAvatar->getUniformScale(); // FIXME - how do we want to handle avatar scale + float uiRadius = _hmdUIRadius; // * myAvatar->getUniformScale(); // FIXME - how do we want to handle avatar scale float instersectionDistance; if (raySphereIntersect(relativeDirection, relativePosition, uiRadius, &instersectionDistance)){ @@ -407,60 +407,6 @@ void CompositorHelper::updateTooltips() { //} } -static const float FADE_DURATION = 500.0f; -static const float FADE_IN_ALPHA = 1.0f; -static const float FADE_OUT_ALPHA = 0.0f; - -void CompositorHelper::startFadeFailsafe(float endValue) { - _fadeStarted = usecTimestampNow(); - _fadeFailsafeEndValue = endValue; - - const int SLIGHT_DELAY = 10; - QTimer::singleShot(FADE_DURATION + SLIGHT_DELAY, [this]{ - checkFadeFailsafe(); - }); -} - -void CompositorHelper::checkFadeFailsafe() { - auto elapsedInFade = usecTimestampNow() - _fadeStarted; - if (elapsedInFade > FADE_DURATION) { - setAlpha(_fadeFailsafeEndValue); - } -} - -void CompositorHelper::fadeIn() { - _fadeInAlpha = true; - - _alphaPropertyAnimation->setDuration(FADE_DURATION); - _alphaPropertyAnimation->setStartValue(_alpha); - _alphaPropertyAnimation->setEndValue(FADE_IN_ALPHA); - _alphaPropertyAnimation->start(); - - // Sometimes, this "QPropertyAnimation" fails to complete the animation, and we end up with a partially faded - // state. So we will also have this fail-safe, where we record the timestamp of the fadeRequest, and the target - // value of the fade, and if after that time we still haven't faded all the way, we will kick it to the final - // fade value - startFadeFailsafe(FADE_IN_ALPHA); -} - -void CompositorHelper::fadeOut() { - _fadeInAlpha = false; - - _alphaPropertyAnimation->setDuration(FADE_DURATION); - _alphaPropertyAnimation->setStartValue(_alpha); - _alphaPropertyAnimation->setEndValue(FADE_OUT_ALPHA); - _alphaPropertyAnimation->start(); - startFadeFailsafe(FADE_OUT_ALPHA); -} - -void CompositorHelper::toggle() { - if (_fadeInAlpha) { - fadeOut(); - } else { - fadeIn(); - } -} - glm::mat4 CompositorHelper::getReticleTransform(const glm::mat4& eyePose, const glm::vec3& headPosition) const { glm::mat4 result; if (isHMD()) { diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.h b/libraries/display-plugins/src/display-plugins/CompositorHelper.h index c0b53b329e..868beec53f 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.h +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.h @@ -38,7 +38,6 @@ const float MAGNIFY_MULT = 2.0f; class CompositorHelper : public QObject, public Dependency { Q_OBJECT - Q_PROPERTY(float alpha READ getAlpha WRITE setAlpha) Q_PROPERTY(bool reticleOverDesktop READ getReticleOverDesktop WRITE setReticleOverDesktop) public: static const uvec2 VIRTUAL_SCREEN_SIZE; @@ -75,13 +74,6 @@ public: void setModelTransform(const Transform& transform) { _modelTransform = transform; } const Transform& getModelTransform() const { return _modelTransform; } - void fadeIn(); - void fadeOut(); - void toggle(); - - float getAlpha() const { return _alpha; } - void setAlpha(float alpha) { _alpha = alpha; } - bool getReticleVisible() const { return _reticleVisible; } void setReticleVisible(bool visible) { _reticleVisible = visible; } @@ -113,7 +105,7 @@ public: void setReticleOverDesktop(bool value) { _isOverDesktop = value; } void setDisplayPlugin(const DisplayPluginPointer& displayPlugin) { _currentDisplayPlugin = displayPlugin; } - void setFrameInfo(uint32_t frame, const glm::mat4& camera) { _currentCamera = camera; _currentFrame = frame; } + void setFrameInfo(uint32_t frame, const glm::mat4& camera) { _currentCamera = camera; } signals: void allowMouseCaptureChanged(); @@ -127,7 +119,6 @@ private: DisplayPluginPointer _currentDisplayPlugin; glm::mat4 _currentCamera; - uint32_t _currentFrame { 0 }; QWidget* _renderingWidget{ nullptr }; //// Support for hovering and tooltips @@ -143,17 +134,7 @@ private: float _textureFov { VIRTUAL_UI_TARGET_FOV.y }; float _textureAspectRatio { VIRTUAL_UI_ASPECT_RATIO }; - float _alpha { 1.0f }; - float _prevAlpha { 1.0f }; - float _fadeInAlpha { true }; - float _oculusUIRadius { 1.0f }; - - quint64 _fadeStarted { 0 }; - float _fadeFailsafeEndValue { 1.0f }; - void checkFadeFailsafe(); - void startFadeFailsafe(float endValue); - - int _reticleQuad; + float _hmdUIRadius { 1.0f }; int _previousBorderWidth { -1 }; int _previousBorderHeight { -1 }; diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index d34b698410..d9ee979777 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -445,25 +445,18 @@ void OpenGLDisplayPlugin::compositeOverlay() { useProgram(_program); // check the alpha - auto overlayAlpha = compositorHelper->getAlpha(); - if (overlayAlpha > 0.0f) { - // set the alpha - Uniform(*_program, _alphaUniform).Set(overlayAlpha); - - // Overlay draw - if (isStereo()) { - Uniform(*_program, _mvpUniform).Set(mat4()); - for_each_eye([&](Eye eye) { - eyeViewport(eye); - drawUnitQuad(); - }); - } else { - // Overlay draw - Uniform(*_program, _mvpUniform).Set(mat4()); + // Overlay draw + if (isStereo()) { + Uniform(*_program, _mvpUniform).Set(mat4()); + for_each_eye([&](Eye eye) { + eyeViewport(eye); drawUnitQuad(); - } + }); + } else { + // Overlay draw + Uniform(*_program, _mvpUniform).Set(mat4()); + drawUnitQuad(); } - Uniform(*_program, _alphaUniform).Set(1.0); } void OpenGLDisplayPlugin::compositePointer() { @@ -471,24 +464,16 @@ void OpenGLDisplayPlugin::compositePointer() { auto compositorHelper = DependencyManager::get(); useProgram(_program); - // check the alpha - auto overlayAlpha = compositorHelper->getAlpha(); - if (overlayAlpha > 0.0f) { - // set the alpha - Uniform(*_program, _alphaUniform).Set(overlayAlpha); - - Uniform(*_program, _mvpUniform).Set(compositorHelper->getReticleTransform(glm::mat4())); - if (isStereo()) { - for_each_eye([&](Eye eye) { - eyeViewport(eye); - drawUnitQuad(); - }); - } else { + Uniform(*_program, _mvpUniform).Set(compositorHelper->getReticleTransform(glm::mat4())); + if (isStereo()) { + for_each_eye([&](Eye eye) { + eyeViewport(eye); drawUnitQuad(); - } + }); + } else { + drawUnitQuad(); } Uniform(*_program, _mvpUniform).Set(mat4()); - Uniform(*_program, _alphaUniform).Set(1.0); } void OpenGLDisplayPlugin::compositeScene() { diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index 4e594d89ed..1616dcdb77 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -254,23 +254,15 @@ void HmdDisplayPlugin::compositeOverlay() { using namespace oglplus; auto compositorHelper = DependencyManager::get(); - // check the alpha useProgram(_program); - auto overlayAlpha = compositorHelper->getAlpha(); - if (overlayAlpha > 0.0f) { - // set the alpha - Uniform(*_program, _alphaUniform).Set(overlayAlpha); - - _sphereSection->Use(); - for_each_eye([&](Eye eye) { - eyeViewport(eye); - auto modelView = glm::inverse(_currentPresentFrameInfo.presentPose * getEyeToHeadTransform(eye)); - auto mvp = _eyeProjections[eye] * modelView; - Uniform(*_program, _mvpUniform).Set(mvp); - _sphereSection->Draw(); - }); - } - Uniform(*_program, _alphaUniform).Set(1.0); + _sphereSection->Use(); + for_each_eye([&](Eye eye) { + eyeViewport(eye); + auto modelView = glm::inverse(_currentPresentFrameInfo.presentPose * getEyeToHeadTransform(eye)); + auto mvp = _eyeProjections[eye] * modelView; + Uniform(*_program, _mvpUniform).Set(mvp); + _sphereSection->Draw(); + }); } void HmdDisplayPlugin::compositePointer() { @@ -280,25 +272,19 @@ void HmdDisplayPlugin::compositePointer() { // check the alpha useProgram(_program); - auto overlayAlpha = compositorHelper->getAlpha(); - if (overlayAlpha > 0.0f) { - // set the alpha - Uniform(*_program, _alphaUniform).Set(overlayAlpha); - // Mouse pointer - _plane->Use(); - // Reconstruct the headpose from the eye poses - auto headPosition = vec3(_currentPresentFrameInfo.presentPose[3]); - for_each_eye([&](Eye eye) { - eyeViewport(eye); - auto eyePose = _currentPresentFrameInfo.presentPose * getEyeToHeadTransform(eye); - auto reticleTransform = compositorHelper->getReticleTransform(eyePose, headPosition); - auto mvp = _eyeProjections[eye] * reticleTransform; - Uniform(*_program, _mvpUniform).Set(mvp); - _plane->Draw(); - }); - } - Uniform(*_program, _alphaUniform).Set(1.0); + // Mouse pointer + _plane->Use(); + // Reconstruct the headpose from the eye poses + auto headPosition = vec3(_currentPresentFrameInfo.presentPose[3]); + for_each_eye([&](Eye eye) { + eyeViewport(eye); + auto eyePose = _currentPresentFrameInfo.presentPose * getEyeToHeadTransform(eye); + auto reticleTransform = compositorHelper->getReticleTransform(eyePose, headPosition); + auto mvp = _eyeProjections[eye] * reticleTransform; + Uniform(*_program, _mvpUniform).Set(mvp); + _plane->Draw(); + }); } void HmdDisplayPlugin::internalPresent() { diff --git a/libraries/ui/src/ErrorDialog.cpp b/libraries/ui/src/ErrorDialog.cpp index ab36ef8d36..fcd73b4cc0 100644 --- a/libraries/ui/src/ErrorDialog.cpp +++ b/libraries/ui/src/ErrorDialog.cpp @@ -22,10 +22,6 @@ QString ErrorDialog::text() const { return _text; } -void ErrorDialog::setVisible(bool v) { - OffscreenQmlDialog::setVisible(v); -} - void ErrorDialog::setText(const QString& arg) { if (arg != _text) { _text = arg; diff --git a/libraries/ui/src/ErrorDialog.h b/libraries/ui/src/ErrorDialog.h index 665090da1a..38954714a7 100644 --- a/libraries/ui/src/ErrorDialog.h +++ b/libraries/ui/src/ErrorDialog.h @@ -30,7 +30,6 @@ public: QString text() const; public slots: - virtual void setVisible(bool v); void setText(const QString& arg); signals: diff --git a/libraries/ui/src/OffscreenQmlDialog.cpp b/libraries/ui/src/OffscreenQmlDialog.cpp index 43514c4761..2d1ca20876 100644 --- a/libraries/ui/src/OffscreenQmlDialog.cpp +++ b/libraries/ui/src/OffscreenQmlDialog.cpp @@ -17,7 +17,7 @@ OffscreenQmlDialog::~OffscreenQmlDialog() { } void OffscreenQmlDialog::hide() { - static_cast(parent())->setVisible(false); + parent()->setProperty(OFFSCREEN_VISIBILITY_PROPERTY, false); } QString OffscreenQmlDialog::title() const { diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp index dfd9056703..fa1a31d196 100644 --- a/libraries/ui/src/OffscreenUi.cpp +++ b/libraries/ui/src/OffscreenUi.cpp @@ -121,32 +121,28 @@ void OffscreenUi::show(const QUrl& url, const QString& name, std::functionfindChild(name); } + if (item) { - item->setVisible(true); + QQmlProperty(item, OFFSCREEN_VISIBILITY_PROPERTY).write(true); } } void OffscreenUi::toggle(const QUrl& url, const QString& name, std::function f) { QQuickItem* item = getRootItem()->findChild(name); - // Already loaded? - if (item) { - emit showDesktop(); - item->setVisible(!item->isVisible()); + if (!item) { + show(url, name, f); return; } - load(url, f); - item = getRootItem()->findChild(name); - if (item && !item->isVisible()) { - emit showDesktop(); - item->setVisible(true); - } + // Already loaded, so just flip the bit + QQmlProperty shownProperty(item, OFFSCREEN_VISIBILITY_PROPERTY); + shownProperty.write(!shownProperty.read().toBool()); } void OffscreenUi::hide(const QString& name) { QQuickItem* item = getRootItem()->findChild(name); if (item) { - item->setVisible(false); + QQmlProperty(item, OFFSCREEN_VISIBILITY_PROPERTY).write(false); } } @@ -345,6 +341,20 @@ QVariant OffscreenUi::inputDialog(const Icon icon, const QString& title, const Q return waitForInputDialogResult(createInputDialog(icon, title, label, current)); } +void OffscreenUi::togglePinned() { + bool invokeResult = QMetaObject::invokeMethod(_desktop, "togglePinned"); + if (!invokeResult) { + qWarning() << "Failed to toggle window visibility"; + } +} + +void OffscreenUi::setPinned(bool pinned) { + bool invokeResult = QMetaObject::invokeMethod(_desktop, "setPinned", Q_ARG(QVariant, pinned)); + if (!invokeResult) { + qWarning() << "Failed to set window visibility"; + } +} + void OffscreenUi::addMenuInitializer(std::function f) { if (!_vrMenu) { _queuedMenuInitializers.push_back(f); diff --git a/libraries/ui/src/OffscreenUi.h b/libraries/ui/src/OffscreenUi.h index 5a16b49491..e1d552c978 100644 --- a/libraries/ui/src/OffscreenUi.h +++ b/libraries/ui/src/OffscreenUi.h @@ -28,6 +28,8 @@ class VrMenu; +#define OFFSCREEN_VISIBILITY_PROPERTY "shown" + class OffscreenUi : public OffscreenQmlSurface, public Dependency { Q_OBJECT @@ -44,6 +46,13 @@ public: void setNavigationFocused(bool focused); void unfocusWindows(); void toggleMenu(const QPoint& screenCoordinates); + + + // Setting pinned to true will hide all overlay elements on the desktop that don't have a pinned flag + void setPinned(bool pinned = true); + + void togglePinned(); + bool eventFilter(QObject* originalDestination, QEvent* event) override; void addMenuInitializer(std::function f); diff --git a/libraries/ui/src/QmlWindowClass.cpp b/libraries/ui/src/QmlWindowClass.cpp index 396d716cda..b8834f0549 100644 --- a/libraries/ui/src/QmlWindowClass.cpp +++ b/libraries/ui/src/QmlWindowClass.cpp @@ -163,8 +163,7 @@ void QmlWindowClass::setVisible(bool visible) { QMetaObject::invokeMethod(targetWindow, "showTabForUrl", Qt::QueuedConnection, Q_ARG(QVariant, _source), Q_ARG(QVariant, visible)); } else { DependencyManager::get()->executeOnUiThread([=] { - targetWindow->setVisible(visible); - //emit visibilityChanged(visible); + targetWindow->setProperty(OFFSCREEN_VISIBILITY_PROPERTY, visible); }); } } diff --git a/libraries/ui/src/Tooltip.cpp b/libraries/ui/src/Tooltip.cpp index 3c0902b378..94e04f34b6 100644 --- a/libraries/ui/src/Tooltip.cpp +++ b/libraries/ui/src/Tooltip.cpp @@ -47,10 +47,6 @@ void Tooltip::setImageURL(const QString& imageURL) { } } -void Tooltip::setVisible(bool visible) { - QQuickItem::setVisible(visible); -} - QString Tooltip::showTip(const QString& title, const QString& description) { const QString newTipId = QUuid().createUuid().toString(); diff --git a/libraries/ui/src/Tooltip.h b/libraries/ui/src/Tooltip.h index d1c7330a74..5e884a7aea 100644 --- a/libraries/ui/src/Tooltip.h +++ b/libraries/ui/src/Tooltip.h @@ -39,8 +39,6 @@ public: static void closeTip(const QString& tipId); public slots: - virtual void setVisible(bool v); - void setTitle(const QString& title); void setDescription(const QString& description); void setImageURL(const QString& imageURL); diff --git a/tests/ui/qml/main.qml b/tests/ui/qml/main.qml index 97f0c0a613..3b2cc264c8 100644 --- a/tests/ui/qml/main.qml +++ b/tests/ui/qml/main.qml @@ -5,7 +5,7 @@ import Qt.labs.settings 1.0 import "../../../interface/resources/qml" //import "../../../interface/resources/qml/windows" -import "../../../interface/resources/qml/windows-uit" +import "../../../interface/resources/qml/windows" import "../../../interface/resources/qml/dialogs" import "../../../interface/resources/qml/hifi" import "../../../interface/resources/qml/hifi/dialogs" @@ -17,6 +17,267 @@ ApplicationWindow { width: 1280 height: 800 title: qsTr("Scratch App") + toolBar: Row { + id: testButtons + anchors { margins: 8; left: parent.left; top: parent.top } + spacing: 8 + property int count: 0 + + property var tabs: []; + property var urls: []; + + // Window visibility + + Button { + text: "restore all" + onClicked: { + for (var i = 0; i < desktop.windows.length; ++i) { + desktop.windows[i].shown = true + } + } + } + Button { + text: "toggle blue visible" + onClicked: { + blue.shown = !blue.shown + } + } + Button { + text: "toggle blue enabled" + onClicked: { + blue.enabled = !blue.enabled + } + } + + Button { + text: "toggle desktop" + onClicked: desktop.toggleVisible() + } + + // Error alerts + /* + Button { + // Message without title. + text: "Show Error" + onClicked: { + var messageBox = desktop.messageBox({ + text: "Diagnostic cycle will be complete in 30 seconds", + icon: hifi.icons.critical, + }); + messageBox.selected.connect(function(button) { + console.log("You clicked " + button) + }) + } + } + Button { + // detailedText is not currently used anywhere in Interface but it is easier to leave in and style good enough. + text: "Show Long Error" + onClicked: { + desktop.messageBox({ + informativeText: "Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds ", + text: "Baloney", + icon: hifi.icons.warning, + detailedText: "sakjd;laskj dksa;dl jka;lsd j;lkjas ;dlkaj s;dlakjd ;alkjda; slkjda; lkjda;lksjd ;alksjd; alksjd ;alksjd; alksjd; alksdjas;ldkjas;lkdja ;kj ;lkasjd; lkj as;dlka jsd;lka jsd;laksjd a" + }); + } + } + */ + + // query + /* + // There is no such desktop.queryBox() function; may need to update test to cover QueryDialog.qml? + Button { + text: "Show Query" + onClicked: { + var queryBox = desktop.queryBox({ + text: "Have you stopped beating your wife?", + placeholderText: "Are you sure?", + // icon: hifi.icons.critical, + }); + queryBox.selected.connect(function(result) { + console.log("User responded with " + result); + }); + + queryBox.canceled.connect(function() { + console.log("User cancelled query box "); + }) + } + } + */ + + // file dialog + + Button { + text: "Open Directory" + property var builder: Component { + FileDialog { selectDirectory: true } + } + + onClicked: { + var fileDialog = builder.createObject(desktop); + fileDialog.canceled.connect(function(){ + console.log("Cancelled") + }) + fileDialog.selectedFile.connect(function(file){ + console.log("Selected " + file) + }) + } + } + Button { + text: "Open File" + property var builder: Component { + FileDialog { + title: "Open File" + filter: "All Files (*.*)" + //filter: "HTML files (*.html);;Other(*.png)" + } + } + + onClicked: { + var fileDialog = builder.createObject(desktop); + fileDialog.canceled.connect(function(){ + console.log("Cancelled") + }) + fileDialog.selectedFile.connect(function(file){ + console.log("Selected " + file) + }) + } + } + /* + */ + + // tabs + /* + Button { + text: "Add Tab" + onClicked: { + console.log(desktop.toolWindow); + desktop.toolWindow.addWebTab({ source: "Foo" }); + desktop.toolWindow.showTabForUrl("Foo", true); + } + } + + Button { + text: "Add Tab 2" + onClicked: { + console.log(desktop.toolWindow); + desktop.toolWindow.addWebTab({ source: "Foo 2" }); + desktop.toolWindow.showTabForUrl("Foo 2", true); + } + } + + Button { + text: "Add Tab 3" + onClicked: { + console.log(desktop.toolWindow); + desktop.toolWindow.addWebTab({ source: "Foo 3" }); + desktop.toolWindow.showTabForUrl("Foo 3", true); + } + } + + Button { + text: "Destroy Tab" + onClicked: { + console.log(desktop.toolWindow); + desktop.toolWindow.removeTabForUrl("Foo"); + } + } + */ + + // Hifi specific stuff + /* + Button { + // Shows the dialog with preferences sections but not each section's preference items + // because Preferences.preferencesByCategory() method is not stubbed out. + text: "Settings > General..." + property var builder: Component { + GeneralPreferencesDialog { } + } + onClicked: { + var runningScripts = builder.createObject(desktop); + } + } + + Button { + text: "Running Scripts" + property var builder: Component { + RunningScripts { } + } + onClicked: { + var runningScripts = builder.createObject(desktop); + } + } + + Button { + text: "Attachments" + property var builder: Component { + AttachmentsDialog { } + } + onClicked: { + var attachmentsDialog = builder.createObject(desktop); + } + } + Button { + // Replicates message box that pops up after selecting new avatar. Includes title. + text: "Confirm Avatar" + onClicked: { + var messageBox = desktop.messageBox({ + title: "Set Avatar", + text: "Would you like to use 'Albert' for your avatar?", + icon: hifi.icons.question, // Test question icon + //icon: hifi.icons.information, // Test informaton icon + //icon: hifi.icons.warning, // Test warning icon + //icon: hifi.icons.critical, // Test critical icon + //icon: hifi.icons.none, // Test no icon + buttons: OriginalDialogs.StandardButton.Ok + OriginalDialogs.StandardButton.Cancel, + defaultButton: OriginalDialogs.StandardButton.Ok + }); + messageBox.selected.connect(function(button) { + console.log("You clicked " + button) + }) + } + } + */ + // bookmarks + /* + Button { + text: "Bookmark Location" + onClicked: { + desktop.inputDialog({ + title: "Bookmark Location", + icon: hifi.icons.placemark, + label: "Name" + }); + } + } + Button { + text: "Delete Bookmark" + onClicked: { + desktop.inputDialog({ + title: "Delete Bookmark", + icon: hifi.icons.placemark, + label: "Select the bookmark to delete", + items: ["Bookmark A", "Bookmark B", "Bookmark C"] + }); + } + } + Button { + text: "Duplicate Bookmark" + onClicked: { + desktop.messageBox({ + title: "Duplicate Bookmark", + icon: hifi.icons.warning, + text: "The bookmark name you entered alread exists in yoru list.", + informativeText: "Would you like to overwrite it?", + buttons: OriginalDialogs.StandardButton.Yes + OriginalDialogs.StandardButton.No, + defaultButton: OriginalDialogs.StandardButton.Yes + }); + } + } + */ + + } + HifiConstants { id: hifi } @@ -35,249 +296,12 @@ ApplicationWindow { } */ - Row { - id: testButtons - anchors { margins: 8; left: parent.left; top: parent.top } - spacing: 8 - property int count: 0 - - property var tabs: []; - property var urls: []; - - Button { - // Shows the dialog with preferences sections but not each section's preference items - // because Preferences.preferencesByCategory() method is not stubbed out. - text: "Settings > General..." - property var builder: Component { - GeneralPreferencesDialog { } - } - onClicked: { - var runningScripts = builder.createObject(desktop); - } - } - - Button { - text: "Running Scripts" - property var builder: Component { - RunningScripts { } - } - onClicked: { - var runningScripts = builder.createObject(desktop); - } - } - - Button { - text: "Attachments" - property var builder: Component { - AttachmentsDialog { } - } - onClicked: { - var attachmentsDialog = builder.createObject(desktop); - } - } - - /* - Button { - text: "restore all" - onClicked: { - for (var i = 0; i < desktop.windows.length; ++i) { - desktop.windows[i].visible = true - } - } - } - Button { - text: "toggle blue visible" - onClicked: { - blue.visible = !blue.visible - } - } - Button { - text: "toggle blue enabled" - onClicked: { - blue.enabled = !blue.enabled - } - } - */ - Button { - // Replicates message box that pops up after selecting new avatar. Includes title. - text: "Confirm Avatar" - onClicked: { - var messageBox = desktop.messageBox({ - title: "Set Avatar", - text: "Would you like to use 'Albert' for your avatar?", - icon: hifi.icons.question, // Test question icon - //icon: hifi.icons.information, // Test informaton icon - //icon: hifi.icons.warning, // Test warning icon - //icon: hifi.icons.critical, // Test critical icon - //icon: hifi.icons.none, // Test no icon - buttons: OriginalDialogs.StandardButton.Ok + OriginalDialogs.StandardButton.Cancel, - defaultButton: OriginalDialogs.StandardButton.Ok - }); - messageBox.selected.connect(function(button) { - console.log("You clicked " + button) - }) - } - } - Button { - // Message without title. - text: "Show Error" - onClicked: { - var messageBox = desktop.messageBox({ - text: "Diagnostic cycle will be complete in 30 seconds", - icon: hifi.icons.critical, - }); - messageBox.selected.connect(function(button) { - console.log("You clicked " + button) - }) - } - } - Button { - // detailedText is not currently used anywhere in Interface but it is easier to leave in and style good enough. - text: "Show Long Error" - onClicked: { - desktop.messageBox({ - informativeText: "Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds Diagnostic cycle will be complete in 30 seconds ", - text: "Baloney", - icon: hifi.icons.warning, - detailedText: "sakjd;laskj dksa;dl jka;lsd j;lkjas ;dlkaj s;dlakjd ;alkjda; slkjda; lkjda;lksjd ;alksjd; alksjd ;alksjd; alksjd; alksdjas;ldkjas;lkdja ;kj ;lkasjd; lkj as;dlka jsd;lka jsd;laksjd a" - }); - } - } - Button { - text: "Bookmark Location" - onClicked: { - desktop.inputDialog({ - title: "Bookmark Location", - icon: hifi.icons.placemark, - label: "Name" - }); - } - } - Button { - text: "Delete Bookmark" - onClicked: { - desktop.inputDialog({ - title: "Delete Bookmark", - icon: hifi.icons.placemark, - label: "Select the bookmark to delete", - items: ["Bookmark A", "Bookmark B", "Bookmark C"] - }); - } - } - Button { - text: "Duplicate Bookmark" - onClicked: { - desktop.messageBox({ - title: "Duplicate Bookmark", - icon: hifi.icons.warning, - text: "The bookmark name you entered alread exists in yoru list.", - informativeText: "Would you like to overwrite it?", - buttons: OriginalDialogs.StandardButton.Yes + OriginalDialogs.StandardButton.No, - defaultButton: OriginalDialogs.StandardButton.Yes - }); - } - } - /* - // There is no such desktop.queryBox() function; may need to update test to cover QueryDialog.qml? - Button { - text: "Show Query" - onClicked: { - var queryBox = desktop.queryBox({ - text: "Have you stopped beating your wife?", - placeholderText: "Are you sure?", - // icon: hifi.icons.critical, - }); - queryBox.selected.connect(function(result) { - console.log("User responded with " + result); - }); - - queryBox.canceled.connect(function() { - console.log("User cancelled query box "); - }) - } - } - */ - Button { - text: "Open Directory" - property var builder: Component { - FileDialog { selectDirectory: true } - } - - onClicked: { - var fileDialog = builder.createObject(desktop); - fileDialog.canceled.connect(function(){ - console.log("Cancelled") - }) - fileDialog.selectedFile.connect(function(file){ - console.log("Selected " + file) - }) - } - } - - Button { - text: "Open File" - property var builder: Component { - FileDialog { - title: "Open File" - filter: "All Files (*.*)" - //filter: "HTML files (*.html);;Other(*.png)" - } - } - - onClicked: { - var fileDialog = builder.createObject(desktop); - fileDialog.canceled.connect(function(){ - console.log("Cancelled") - }) - fileDialog.selectedFile.connect(function(file){ - console.log("Selected " + file) - }) - } - } - - Button { - text: "Add Tab" - onClicked: { - console.log(desktop.toolWindow); - desktop.toolWindow.addWebTab({ source: "Foo" }); - desktop.toolWindow.showTabForUrl("Foo", true); - } - } - - Button { - text: "Add Tab 2" - onClicked: { - console.log(desktop.toolWindow); - desktop.toolWindow.addWebTab({ source: "Foo 2" }); - desktop.toolWindow.showTabForUrl("Foo 2", true); - } - } - - Button { - text: "Add Tab 3" - onClicked: { - console.log(desktop.toolWindow); - desktop.toolWindow.addWebTab({ source: "Foo 3" }); - desktop.toolWindow.showTabForUrl("Foo 3", true); - } - } - - Button { - text: "Destroy Tab" - onClicked: { - console.log(desktop.toolWindow); - desktop.toolWindow.removeTabForUrl("Foo"); - } - } - - } - /* Window { id: blue closable: true visible: true resizable: true - destroyOnInvisible: false + destroyOnHidden: false width: 100; height: 100 x: 1280 / 2; y: 720 / 2 @@ -296,32 +320,33 @@ ApplicationWindow { Component.onDestruction: console.log("Blue destroyed") } } - */ - /* + + + Rectangle { width: 100; height: 100; x: 100; y: 100; color: "#00f" } + Window { id: green alwaysOnTop: true + frame: HiddenFrame{} + hideBackground: true closable: true visible: true resizable: false x: 1280 / 2; y: 720 / 2 - Settings { - category: "TestWindow.Green" - property alias x: green.x - property alias y: green.y - property alias width: green.width - property alias height: green.height - } width: 100; height: 100 - Rectangle { anchors.fill: parent; color: "green" } + Rectangle { + color: "#0f0" + width: green.width; + height: green.height; + } } - +/* Window { id: yellow - objectName: "Yellow" closable: true visible: true resizable: true + x: 100; y: 100 width: 100; height: 100 Rectangle { anchors.fill: parent @@ -329,7 +354,7 @@ ApplicationWindow { color: "yellow" } } - */ +*/ } Action { diff --git a/tests/ui/qmlscratch.pro b/tests/ui/qmlscratch.pro index 417d7dad5b..95be6a480e 100644 --- a/tests/ui/qmlscratch.pro +++ b/tests/ui/qmlscratch.pro @@ -18,6 +18,7 @@ DISTFILES += \ qml/*.qml \ ../../interface/resources/qml/*.qml \ ../../interface/resources/qml/controls/*.qml \ + ../../interface/resources/qml/controls-uit/*.qml \ ../../interface/resources/qml/dialogs/*.qml \ ../../interface/resources/qml/dialogs/fileDialog/*.qml \ ../../interface/resources/qml/dialogs/preferences/*.qml \ @@ -25,9 +26,9 @@ DISTFILES += \ ../../interface/resources/qml/desktop/*.qml \ ../../interface/resources/qml/menus/*.qml \ ../../interface/resources/qml/styles/*.qml \ + ../../interface/resources/qml/styles-uit/*.qml \ ../../interface/resources/qml/windows/*.qml \ ../../interface/resources/qml/hifi/*.qml \ ../../interface/resources/qml/hifi/dialogs/*.qml \ ../../interface/resources/qml/hifi/dialogs/preferences/*.qml \ ../../interface/resources/qml/hifi/overlays/*.qml - From ed5d904b3d87b69c39fa2812dcd5e47623a6b28c Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 2 Jun 2016 09:50:58 -0700 Subject: [PATCH 08/93] Hide overlays when driving --- interface/resources/qml/desktop/Desktop.qml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/interface/resources/qml/desktop/Desktop.qml b/interface/resources/qml/desktop/Desktop.qml index 73f8a17bb0..c3b1b0ad6e 100644 --- a/interface/resources/qml/desktop/Desktop.qml +++ b/interface/resources/qml/desktop/Desktop.qml @@ -264,6 +264,10 @@ FocusScope { pinned = !pinned } + function setPinned(newPinned) { + pinned = newPinned + } + onPinnedChanged: { if (pinned) { From e9b359feb1afcb7ecbc27edd15fa1c8859355422 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 2 Jun 2016 19:15:19 -0700 Subject: [PATCH 09/93] Make QML overlays disappear, use fade for non-window types --- interface/resources/qml/desktop/Desktop.qml | 39 ++++++++++++++----- .../resources/qml/windows/ModalWindow.qml | 2 +- scripts/system/libraries/toolBars.js | 2 +- 3 files changed, 32 insertions(+), 11 deletions(-) diff --git a/interface/resources/qml/desktop/Desktop.qml b/interface/resources/qml/desktop/Desktop.qml index c3b1b0ad6e..241cd91611 100644 --- a/interface/resources/qml/desktop/Desktop.qml +++ b/interface/resources/qml/desktop/Desktop.qml @@ -268,24 +268,45 @@ FocusScope { pinned = newPinned } - onPinnedChanged: { + property real unpinnedAlpha: 1.0; + Behavior on unpinnedAlpha { + NumberAnimation { + easing.type: Easing.Linear; + duration: 300 + } + } + + state: "NORMAL" + states: [ + State { + name: "NORMAL" + PropertyChanges { target: desktop; unpinnedAlpha: 1.0 } + }, + State { + name: "PINNED" + PropertyChanges { target: desktop; unpinnedAlpha: 0.0 } + } + ] + + transitions: [ + Transition { + NumberAnimation { properties: "unpinnedAlpha"; duration: 300 } + } + ] + + onPinnedChanged: { if (pinned) { + // recalculate our non-pinned children hiddenChildren = d.findMatchingChildren(desktop, function(child){ return !d.isTopLevelWindow(child) && child.visible; }); hiddenChildren.forEach(function(child){ - child.visible = false; + child.opacity = Qt.binding(function(){ return desktop.unpinnedAlpha }); }); - } else { - hiddenChildren.forEach(function(child){ - if (child) { - child.visible = true; - } - }); - hiddenChildren = []; } + state = pinned ? "PINNED" : "NORMAL" } onShowDesktop: pinned = false diff --git a/interface/resources/qml/windows/ModalWindow.qml b/interface/resources/qml/windows/ModalWindow.qml index 144165e4e1..f79dc9084f 100644 --- a/interface/resources/qml/windows/ModalWindow.qml +++ b/interface/resources/qml/windows/ModalWindow.qml @@ -16,7 +16,7 @@ Window { id: window modality: Qt.ApplicationModal destroyOnCloseButton: true - destroyOnInvisible: true + destroyOnHidden: true frame: ModalFrame { } property int colorScheme: hifi.colorSchemes.light diff --git a/scripts/system/libraries/toolBars.js b/scripts/system/libraries/toolBars.js index 9efe533457..f021f456bf 100644 --- a/scripts/system/libraries/toolBars.js +++ b/scripts/system/libraries/toolBars.js @@ -256,7 +256,7 @@ ToolBar = function(x, y, direction, optionalPersistenceKey, optionalInitialPosit y: y - ToolBar.SPACING }); } - this.save(); + //this.save(); } this.setAlpha = function(alpha, tool) { From 4e49f3f94297a54d8021cca6cf77325be1cef855 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 2 Jun 2016 19:54:25 -0700 Subject: [PATCH 10/93] Fix browser and update dialogs --- interface/resources/qml/Browser.qml | 67 ++++++++++++++---------- interface/resources/qml/UpdateDialog.qml | 4 +- tests/ui/qml/main.qml | 11 ++++ 3 files changed, 52 insertions(+), 30 deletions(-) diff --git a/interface/resources/qml/Browser.qml b/interface/resources/qml/Browser.qml index efa2063fe8..01e1bb7680 100644 --- a/interface/resources/qml/Browser.qml +++ b/interface/resources/qml/Browser.qml @@ -2,8 +2,8 @@ import QtQuick 2.3 import QtQuick.Controls 1.2 import QtWebEngine 1.1 -import "controls" -import "styles" +import "controls-uit" +import "styles-uit" import "windows" Window { @@ -15,7 +15,9 @@ Window { width: 800 height: 600 property alias webView: webview - + x: 100 + y: 100 + Component.onCompleted: { shown = true addressBar.text = webview.url @@ -30,15 +32,9 @@ Window { Item { id:item - anchors.fill: parent - Rectangle { - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - anchors.bottom: webview.top - color: "white" - } - + width: pane.contentWidth + implicitHeight: pane.scrollHeight + Row { id: buttons spacing: 4 @@ -46,25 +42,37 @@ Window { anchors.topMargin: 8 anchors.left: parent.left anchors.leftMargin: 8 - FontAwesome { - id: back; text: "\uf0a8"; size: 48; enabled: webview.canGoBack; + HiFiGlyphs { + id: back; + enabled: webview.canGoBack; + text: hifi.glyphs.backward color: enabled ? hifi.colors.text : hifi.colors.disabledText + size: 48 MouseArea { anchors.fill: parent; onClicked: webview.goBack() } } - FontAwesome { - id: forward; text: "\uf0a9"; size: 48; enabled: webview.canGoForward; + + HiFiGlyphs { + id: forward; + enabled: webview.canGoForward; + text: hifi.glyphs.forward color: enabled ? hifi.colors.text : hifi.colors.disabledText - MouseArea { anchors.fill: parent; onClicked: webview.goBack() } + size: 48 + MouseArea { anchors.fill: parent; onClicked: webview.goForward() } } - FontAwesome { - id: reload; size: 48; text: webview.loading ? "\uf057" : "\uf021" - MouseArea { anchors.fill: parent; onClicked: webview.loading ? webview.stop() : webview.reload() } + + HiFiGlyphs { + id: reload; + enabled: webview.canGoForward; + text: webview.loading ? hifi.glyphs.close : hifi.glyphs.reload + color: enabled ? hifi.colors.text : hifi.colors.disabledText + size: 48 + MouseArea { anchors.fill: parent; onClicked: webview.goForward() } } } - Border { + Item { + id: border height: 48 - radius: 8 anchors.top: parent.top anchors.topMargin: 8 anchors.right: parent.right @@ -86,15 +94,18 @@ Window { onSourceChanged: console.log("Icon url: " + source) } } - - TextInput { + + TextField { id: addressBar anchors.right: parent.right anchors.rightMargin: 8 anchors.left: barIcon.right anchors.leftMargin: 0 anchors.verticalCenter: parent.verticalCenter - + focus: true + colorScheme: hifi.colorSchemes.dark + placeholderText: "Enter URL" + Component.onCompleted: scriptsModel.filterRegExp = new RegExp("^.*$", "i") Keys.onPressed: { switch(event.key) { case Qt.Key_Enter: @@ -110,7 +121,7 @@ Window { } } - WebView { + WebEngineView { id: webview url: "http://highfidelity.com" anchors.top: buttons.bottom @@ -119,7 +130,7 @@ Window { anchors.left: parent.left anchors.right: parent.right onLoadingChanged: { - if (loadRequest.status == WebEngineView.LoadSucceededStatus) { + if (loadRequest.status === WebEngineView.LoadSucceededStatus) { addressBar.text = loadRequest.url } } @@ -127,7 +138,7 @@ Window { console.log("New icon: " + icon) } - profile: desktop.browserProfile + //profile: desktop.browserProfile } diff --git a/interface/resources/qml/UpdateDialog.qml b/interface/resources/qml/UpdateDialog.qml index 4cb5b206c6..52fdab25f9 100644 --- a/interface/resources/qml/UpdateDialog.qml +++ b/interface/resources/qml/UpdateDialog.qml @@ -3,8 +3,8 @@ import QtQuick 2.3 import QtQuick.Controls 1.3 import QtQuick.Controls.Styles 1.3 import QtGraphicalEffects 1.0 -import "controls" -import "styles" +import "controls-uit" +import "styles-uit" import "windows" Window { diff --git a/tests/ui/qml/main.qml b/tests/ui/qml/main.qml index 3b2cc264c8..d752734de4 100644 --- a/tests/ui/qml/main.qml +++ b/tests/ui/qml/main.qml @@ -105,6 +105,16 @@ ApplicationWindow { } */ + // Browser + + Button { + text: "Open Browser" + onClicked: builder.createObject(desktop); + property var builder: Component { + Browser {} + } + } + // file dialog Button { @@ -358,6 +368,7 @@ ApplicationWindow { } Action { + id: openBrowserAction text: "Open Browser" shortcut: "Ctrl+Shift+X" onTriggered: { From 3e919cb7ac4d78b3661b020e9f577a0978c680eb Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Fri, 3 Jun 2016 10:42:02 -0700 Subject: [PATCH 11/93] cleanup --- .../controllers/handControllerPointer.js | 59 +++++++++---------- 1 file changed, 28 insertions(+), 31 deletions(-) diff --git a/scripts/system/controllers/handControllerPointer.js b/scripts/system/controllers/handControllerPointer.js index 5cbde82654..e70f704ce7 100644 --- a/scripts/system/controllers/handControllerPointer.js +++ b/scripts/system/controllers/handControllerPointer.js @@ -27,6 +27,7 @@ // UTILITIES ------------- // +function ignore() { } // Utility to make it easier to setup and disconnect cleanly. function setupHandler(event, handler) { @@ -106,7 +107,7 @@ function Trigger() { }; that.full = function () { return (that.state === 'full') ? 1.0 : 0.0; - } + }; } // VERTICAL FIELD OF VIEW --------- @@ -229,10 +230,9 @@ function updateSeeking() { } averageMouseVelocity = lastIntegration = 0; var lookAt2D = HMD.getHUDLookAtPosition2D(); - if (!lookAt2D) { - // FIXME - determine if this message is useful but make it so it doesn't spam the - // log in the case that it is happening - //print('Cannot seek without lookAt position'); + if (!lookAt2D) { // If this happens, something has gone terribly wrong. + print('Cannot seek without lookAt position'); + isSeeking = false; return; } // E.g., if parallel to location in HUD var copy = Reticle.position; @@ -305,7 +305,7 @@ var leftTrigger = new Trigger(); var rightTrigger = new Trigger(); var activeTrigger = rightTrigger; var activeHand = Controller.Standard.RightHand; -function toggleHand() { +function toggleHand() { // unequivocally switch which hand controls mouse position if (activeHand === Controller.Standard.RightHand) { activeHand = Controller.Standard.LeftHand; activeTrigger = leftTrigger; @@ -314,6 +314,13 @@ function toggleHand() { activeTrigger = rightTrigger; } } +function makeToggleAction(hand) { // return a function(0|1) that makes the specified hand control mouse when 1 + return function (on) { + if (on && (activeHand !== hand)) { + toggleHand(); + } + }; +} var clickMapping = Controller.newMapping(Script.resolvePath('') + '-click'); Script.scriptEnding.connect(clickMapping.disable); @@ -321,25 +328,14 @@ Script.scriptEnding.connect(clickMapping.disable); // Gather the trigger data for smoothing. clickMapping.from(Controller.Standard.RT).peek().to(rightTrigger.triggerPress); clickMapping.from(Controller.Standard.LT).peek().to(leftTrigger.triggerPress); -// The next two lines will be removed soon. Right now I want both trigger and button so we can compare. -clickMapping.from(Controller.Standard.RightPrimaryThumb).peek().to(Controller.Actions.ReticleClick); -clickMapping.from(Controller.Standard.LeftPrimaryThumb).peek().to(Controller.Actions.ReticleClick); -// Full somoothed trigger is a click. +// Full smoothed trigger is a click. clickMapping.from(rightTrigger.full).to(Controller.Actions.ReticleClick); clickMapping.from(leftTrigger.full).to(Controller.Actions.ReticleClick); clickMapping.from(Controller.Standard.RightSecondaryThumb).peek().to(Controller.Actions.ContextMenu); clickMapping.from(Controller.Standard.LeftSecondaryThumb).peek().to(Controller.Actions.ContextMenu); // Partial smoothed trigger is activation. -clickMapping.from(rightTrigger.partial).to(function (on) { - if (on && (activeHand !== Controller.Standard.RightHand)) { - toggleHand(); - } -}); -clickMapping.from(leftTrigger.partial).to(function (on) { - if (on && (activeHand !== Controller.Standard.LeftHand)) { - toggleHand(); - } -}); +clickMapping.from(rightTrigger.partial).to(makeToggleAction(Controller.Standard.RightHand)); +clickMapping.from(leftTrigger.partial).to(makeToggleAction(Controller.Standard.LeftHand)); clickMapping.enable(); // VISUAL AID ----------- @@ -374,6 +370,7 @@ function turnOffVisualization(optionalEnableClicks) { // because we're showing c } var MAX_RAY_SCALE = 32000; // Anything large. It's a scale, not a distance. function updateVisualization(controllerPosition, controllerDirection, hudPosition3d, hudPosition2d) { + ignore(controllerPosition, controllerDirection, hudPosition2d); // Show an indication of where the cursor will appear when crossing a HUD element, // and where in-world clicking will occur. // @@ -408,21 +405,21 @@ function updateVisualization(controllerPosition, controllerDirection, hudPositio function update() { var now = Date.now(); if (!handControllerLockOut.expired(now)) { - return turnOffVisualization(); - } // Let them use mouse it in peace. + return turnOffVisualization(); // Let them use mouse it in peace. + } if (!Menu.isOptionChecked("First Person")) { - return turnOffVisualization(); - } // What to do? menus can be behind hand! - if (!Window.hasFocus()) { // Don't mess with other apps - return turnOffVisualization(); + return turnOffVisualization(); // What to do? menus can be behind hand! + } + if (!Window.hasFocus()) { + return turnOffVisualization(); // Don't mess with other apps } leftTrigger.update(); rightTrigger.update(); var controllerPose = Controller.getPoseValue(activeHand); // Valid if any plugged-in hand controller is "on". (uncradled Hydra, green-lighted Vive...) if (!controllerPose.valid) { - return turnOffVisualization(); - } // Controller is cradled. + return turnOffVisualization(); // Controller is cradled. + } var controllerPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, controllerPose.translation), MyAvatar.position); // This gets point direction right, but if you want general quaternion it would be more complicated: @@ -430,9 +427,9 @@ function update() { var hudPoint3d = calculateRayUICollisionPoint(controllerPosition, controllerDirection); if (!hudPoint3d) { - // FIXME - determine if this message is useful but make it so it doesn't spam the - // log in the case that it is happening - //print('Controller is parallel to HUD'); + if (Menu.isOptionChecked("Overlays")) { // With our hud resetting strategy, hudPoint3d should be valid here + print('Controller is parallel to HUD'); // so let us know that our assumptions are wrong. + } return turnOffVisualization(); } var hudPoint2d = overlayFromWorldPoint(hudPoint3d); From 247286c71f41ecc97dccdf57f7111175ad48c36e Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Fri, 3 Jun 2016 11:55:33 -0700 Subject: [PATCH 12/93] Basic address bar toggle button. --- scripts/defaultScripts.js | 1 + scripts/system/goto.js | 47 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 scripts/system/goto.js diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index 2a050d183e..4f6b5ca0e6 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -13,6 +13,7 @@ Script.load("system/progress.js"); Script.load("system/away.js"); Script.load("system/users.js"); Script.load("system/examples.js"); +Script.load("system/goto.js"); Script.load("system/edit.js"); Script.load("system/selectAudioDevice.js"); Script.load("system/notifications.js"); diff --git a/scripts/system/goto.js b/scripts/system/goto.js new file mode 100644 index 0000000000..75d9829905 --- /dev/null +++ b/scripts/system/goto.js @@ -0,0 +1,47 @@ +// +// goto.js +// scripts/system/ +// +// Created by Howard Stearns on 2 Jun 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +Script.include("libraries/toolBars.js"); + +function initialPosition(windowDimensions, toolbar) { + return { + x: windowDimensions.x / 2 - Tool.IMAGE_WIDTH, + y: windowDimensions.y + }; +} +var toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL, "highfidelity.goto.toolbar", initialPosition, { + x: -Tool.IMAGE_WIDTH / 2, + y: -Tool.IMAGE_HEIGHT +}); +var button = toolBar.addTool({ + imageURL: Script.resolvePath("assets/images/tools/directory-01.svg"), + subImage: { + x: 0, + y: Tool.IMAGE_WIDTH, + width: Tool.IMAGE_WIDTH, + height: Tool.IMAGE_HEIGHT + }, + width: Tool.IMAGE_WIDTH, + height: Tool.IMAGE_HEIGHT, + alpha: 0.9, + visible: true, + showButtonDown: true +}); +function onMousePress (event) { + if (event.isLeftButton && button === toolBar.clicked(Overlays.getOverlayAtPoint(event))) { + DialogsManager.toggleAddressBar(); + } +}; +Controller.mousePressEvent.connect(onMousePress) +Script.scriptEnding.connect(function () { + Controller.mousePressEvent.disconnect(onMousePress) + toolBar.cleanup(); +}); From 68819561175d8495be22975dafd39b784ced661e Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Fri, 3 Jun 2016 14:57:46 -0700 Subject: [PATCH 13/93] Basic HMD toggle button. --- scripts/defaultScripts.js | 1 + .../assets/images/tools/hmd-switch-01.svg | 102 ++++++++++++++++++ scripts/system/hmd.js | 57 ++++++++++ 3 files changed, 160 insertions(+) create mode 100644 scripts/system/assets/images/tools/hmd-switch-01.svg create mode 100644 scripts/system/hmd.js diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index 2a050d183e..b10d1c6f28 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -13,6 +13,7 @@ Script.load("system/progress.js"); Script.load("system/away.js"); Script.load("system/users.js"); Script.load("system/examples.js"); +Script.load("system/hmd.js"); Script.load("system/edit.js"); Script.load("system/selectAudioDevice.js"); Script.load("system/notifications.js"); diff --git a/scripts/system/assets/images/tools/hmd-switch-01.svg b/scripts/system/assets/images/tools/hmd-switch-01.svg new file mode 100644 index 0000000000..15ecb02b6b --- /dev/null +++ b/scripts/system/assets/images/tools/hmd-switch-01.svg @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/system/hmd.js b/scripts/system/hmd.js new file mode 100644 index 0000000000..277af68315 --- /dev/null +++ b/scripts/system/hmd.js @@ -0,0 +1,57 @@ +// +// hmd.js +// scripts/system/ +// +// Created by Howard Stearns on 2 Jun 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +Script.include("libraries/toolBars.js"); + +var headset; // The preferred headset. Default to the first one found in the following list. +var displayMenuName = "Display"; +var desktopMenuItemName = "Desktop"; +['OpenVR (Vive)', 'Oculus Rift'].forEach(function (name) { + if (!headset && Menu.menuItemExists(displayMenuName, name)) { + headset = name; + } +}); + +function initialPosition(windowDimensions, toolbar) { + return { + x: windowDimensions.x / 2 + (2 * Tool.IMAGE_WIDTH), + y: windowDimensions.y + }; +} +var toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL, "highfidelity.hmd.toolbar", initialPosition, { + x: -Tool.IMAGE_WIDTH / 2, + y: -Tool.IMAGE_HEIGHT +}); +var button = toolBar.addTool({ + imageURL: Script.resolvePath("assets/images/tools/hmd-switch-01.svg"), + subImage: { + x: 0, + y: Tool.IMAGE_WIDTH, + width: Tool.IMAGE_WIDTH, + height: Tool.IMAGE_HEIGHT + }, + width: Tool.IMAGE_WIDTH, + height: Tool.IMAGE_HEIGHT, + alpha: 0.9, + visible: true, + showButtonDown: true +}); +function onMousePress (event) { + if (event.isLeftButton && button === toolBar.clicked(Overlays.getOverlayAtPoint(event))) { + var isDesktop = Menu.isOptionChecked(desktopMenuItemName); + Menu.setIsOptionChecked(isDesktop ? headset : desktopMenuItemName, true); + } +}; +Controller.mousePressEvent.connect(onMousePress) +Script.scriptEnding.connect(function () { + Controller.mousePressEvent.disconnect(onMousePress) + toolBar.cleanup(); +}); From c9f440a49ddc59b357b807af6e81addd1255196a Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 1 Jun 2016 09:53:06 -0700 Subject: [PATCH 14/93] handControllerGrab: initial rendering of equip hot-spots * When trigger is depressed, any entities that are marked with a wearable equip-point will become highlighted by a wireframe box (temporary art). * The grab state machine is now defined by the CONTROLLER_STATE_MACHINE object. this includes each state's name and updateMethod name. * Support was added for entry and exit methods when changing states, this functionality is used to draw and delete the hot spots when entering and exiting the searching state. --- .../system/controllers/handControllerGrab.js | 280 +++++++++++------- 1 file changed, 181 insertions(+), 99 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index be4bffe58c..0561e73e1f 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -92,6 +92,7 @@ var EQUIP_SPRING_TIMEFRAME = 0.4; // how quickly objects move to their new posit // other constants // +var HOTSPOT_DRAW_DISTANCE = 10; var RIGHT_HAND = 1; var LEFT_HAND = 0; @@ -179,47 +180,85 @@ var COLLIDES_WITH_WHILE_MULTI_GRABBED = "dynamic"; var HEART_BEAT_INTERVAL = 5 * MSECS_PER_SEC; var HEART_BEAT_TIMEOUT = 15 * MSECS_PER_SEC; -function stateToName(state) { - switch (state) { - case STATE_OFF: - return "off"; - case STATE_SEARCHING: - return "searching"; - case STATE_HOLD_SEARCHING: - return "hold_searching"; - case STATE_DISTANCE_HOLDING: - return "distance_holding"; - case STATE_CONTINUE_DISTANCE_HOLDING: - return "continue_distance_holding"; - case STATE_NEAR_GRABBING: - return "near_grabbing"; - case STATE_CONTINUE_NEAR_GRABBING: - return "continue_near_grabbing"; - case STATE_NEAR_TRIGGER: - return "near_trigger"; - case STATE_CONTINUE_NEAR_TRIGGER: - return "continue_near_trigger"; - case STATE_FAR_TRIGGER: - return "far_trigger"; - case STATE_CONTINUE_FAR_TRIGGER: - return "continue_far_trigger"; - case STATE_RELEASE: - return "release"; - case STATE_EQUIP: - return "equip"; - case STATE_HOLD: - return "hold"; - case STATE_CONTINUE_HOLD: - return "continue_hold"; - case STATE_CONTINUE_EQUIP: - return "continue_equip"; - case STATE_WAITING_FOR_EQUIP_THUMB_RELEASE: - return "waiting_for_equip_thumb_release"; - case STATE_WAITING_FOR_RELEASE_THUMB_RELEASE: - return "waiting_for_release_thumb_release"; - } +var CONTROLLER_STATE_MACHINE = {}; - return "unknown"; +CONTROLLER_STATE_MACHINE[STATE_OFF] = { + name: "off", + updateMethod: "off" +}; +CONTROLLER_STATE_MACHINE[STATE_SEARCHING] = { + name: "searching", + updateMethod: "search", + enterMethod: "searchEnter", + exitMethod: "searchExit" +}; +CONTROLLER_STATE_MACHINE[STATE_HOLD_SEARCHING] = { + name: "hold_searching", + updateMethod: "search" +}; +CONTROLLER_STATE_MACHINE[STATE_DISTANCE_HOLDING] = { + name: "distance_holding", + updateMethod: "distanceHolding" +}; +CONTROLLER_STATE_MACHINE[STATE_CONTINUE_DISTANCE_HOLDING] = { + name: "continue_distance_holding", + updateMethod: "continueDistanceHolding" +}; +CONTROLLER_STATE_MACHINE[STATE_NEAR_GRABBING] = { + name: "near_grabbing", + updateMethod: "nearGrabbing" +}; +CONTROLLER_STATE_MACHINE[STATE_EQUIP] = { + name: "equip", + updateMethod: "nearGrabbing" +}; +CONTROLLER_STATE_MACHINE[STATE_HOLD] = { + name: "hold", + updateMethod: "nearGrabbing" +}; +CONTROLLER_STATE_MACHINE[STATE_CONTINUE_NEAR_GRABBING] = { + name: "continue_near_grabbing", + updateMethod: "continueNearGrabbing" +}; +CONTROLLER_STATE_MACHINE[STATE_CONTINUE_HOLD] = { + name: "continue_hold", + updateMethod: "continueNearGrabbing" +}; +CONTROLLER_STATE_MACHINE[STATE_CONTINUE_EQUIP] = { + name: "continue_equip", + updateMethod: "continueNearGrabbing" +}; +CONTROLLER_STATE_MACHINE[STATE_NEAR_TRIGGER] = { + name: "near_trigger", + updateMethod: "nearTrigger" +}; +CONTROLLER_STATE_MACHINE[STATE_CONTINUE_NEAR_TRIGGER] = { + name: "continue_near_trigger", + updateMethod: "continueNearTrigger" +}; +CONTROLLER_STATE_MACHINE[STATE_FAR_TRIGGER] = { + name: "far_trigger", + updateMethod: "farTrigger" +}; +CONTROLLER_STATE_MACHINE[STATE_CONTINUE_FAR_TRIGGER] = { + name: "continue_far_trigger", + updateMethod: "continueFarTrigger" +}; +CONTROLLER_STATE_MACHINE[STATE_RELEASE] = { + name: "release", + updateMethod: "release" +}; +CONTROLLER_STATE_MACHINE[STATE_WAITING_FOR_EQUIP_THUMB_RELEASE] = { + name: "waiting_for_equip_thumb_release", + updateMethod: "waitingForEquipThumbRelease" +}; +CONTROLLER_STATE_MACHINE[STATE_WAITING_FOR_RELEASE_THUMB_RELEASE] = { + name: "waiting_for_release_thumb_release", + updateMethod: "waitingForReleaseThumbRelease" +}; + +function stateToName(state) { + return CONTROLLER_STATE_MACHINE[state] ? CONTROLLER_STATE_MACHINE[state].name : "???"; } function getTag() { @@ -249,6 +288,14 @@ function entityIsGrabbedByOther(entityID) { return false; } +function propsArePhysical(props) { + if (!props.dynamic) { + return false; + } + var isPhysical = (props.shapeType && props.shapeType != 'none'); + return isPhysical; +} + // If another script is managing the reticle (as is done by HandControllerPointer), we should not be setting it here, // and we should not be showing lasers when someone else is using the Reticle to indicate a 2D minor mode. var EXTERNALLY_MANAGED_2D_MINOR_MODE = true; @@ -314,55 +361,22 @@ function MyController(hand) { this.update = function() { this.updateSmoothedTrigger(); + if (isIn2DMode()) { _this.turnOffVisualizations(); return; } - switch (this.state) { - case STATE_OFF: - this.off(); - break; - case STATE_SEARCHING: - case STATE_HOLD_SEARCHING: - this.search(); - break; - case STATE_DISTANCE_HOLDING: - this.distanceHolding(); - break; - case STATE_CONTINUE_DISTANCE_HOLDING: - this.continueDistanceHolding(); - break; - case STATE_NEAR_GRABBING: - case STATE_EQUIP: - case STATE_HOLD: - this.nearGrabbing(); - break; - case STATE_WAITING_FOR_EQUIP_THUMB_RELEASE: - this.waitingForEquipThumbRelease(); - break; - case STATE_WAITING_FOR_RELEASE_THUMB_RELEASE: - this.waitingForReleaseThumbRelease(); - break; - case STATE_CONTINUE_NEAR_GRABBING: - case STATE_CONTINUE_HOLD: - case STATE_CONTINUE_EQUIP: - this.continueNearGrabbing(); - break; - case STATE_NEAR_TRIGGER: - this.nearTrigger(); - break; - case STATE_CONTINUE_NEAR_TRIGGER: - this.continueNearTrigger(); - break; - case STATE_FAR_TRIGGER: - this.farTrigger(); - break; - case STATE_CONTINUE_FAR_TRIGGER: - this.continueFarTrigger(); - break; - case STATE_RELEASE: - this.release(); - break; + + if (CONTROLLER_STATE_MACHINE[this.state]) { + var updateMethodName = CONTROLLER_STATE_MACHINE[this.state].updateMethod; + var updateMethod = this[updateMethodName]; + if (updateMethod) { + updateMethod.call(this); + } else { + print("WARNING: could not find updateMethod for state " + stateToName(this.state)); + } + } else { + print("WARNING: could not find state " + this.state + " " + CONTROLLER_STATE_MACHINE[this.state]); } }; @@ -374,9 +388,33 @@ function MyController(hand) { this.setState = function(newState) { this.grabSphereOff(); if (WANT_DEBUG || WANT_DEBUG_STATE) { - print("STATE (" + this.hand + "): " + stateToName(this.state) + " --> " + - stateToName(newState) + ", hand: " + this.hand); + var oldStateName = stateToName(this.state); + var newStateName = stateToName(newState); + print("STATE (" + this.hand + "): " + oldStateName + " --> " + newStateName); } + + // exit the old state + if (CONTROLLER_STATE_MACHINE[this.state]) { + var exitMethodName = CONTROLLER_STATE_MACHINE[this.state].exitMethod; + var exitMethod = this[exitMethodName]; + if (exitMethod) { + exitMethod.call(this); + } + } else { + print("WARNING: could not find this.state " + this.state); + } + + // enter the new state + if (CONTROLLER_STATE_MACHINE[newState]) { + var enterMethodName = CONTROLLER_STATE_MACHINE[newState].enterMethod; + var enterMethod = this[enterMethodName]; + if (enterMethod) { + enterMethod.call(this); + } + } else { + print("WARNING: could not find newState " + newState); + } + this.state = newState; }; @@ -759,14 +797,6 @@ function MyController(hand) { } }; - this.propsArePhysical = function(props) { - if (!props.dynamic) { - return false; - } - var isPhysical = (props.shapeType && props.shapeType != 'none'); - return isPhysical; - } - this.turnOffVisualizations = function() { if (USE_ENTITY_LINES_FOR_SEARCHING === true || USE_ENTITY_LINES_FOR_MOVING === true) { this.lineOff(); @@ -852,6 +882,58 @@ function MyController(hand) { } }; + this.searchEnter = function() { + this.equipHotspotOverlays = []; + + // find entities near the avatar that might be equipable. + var entities = Entities.findEntities(MyAvatar.position, HOTSPOT_DRAW_DISTANCE); + var i, l = entities.length; + for (i = 0; i < l; i++) { + + // is this entity equipable? + var grabData = getEntityCustomData(GRABBABLE_DATA_KEY, entities[i], undefined); + var grabProps = Entities.getEntityProperties(entities[i], GRABBABLE_PROPERTIES); + if (grabData) { + + var hotspotPos = grabProps.position; + + // does this entity have an equip point? + var wearableData = getEntityCustomData("wearable", entities[i], undefined); + if (wearableData) { + var handJointName = this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"; + if (wearableData[handJointName]) { + // draw the hotspot around the equip point. + hotspotPos = wearableData[handJointName][0]; + } + } + + // draw a hotspot! + this.equipHotspotOverlays.push(Overlays.addOverlay("cube", { + position: hotspotPos, + size: 0.1, + color: { red: 90, green: 255, blue: 90 }, + alpha: 1, + solid: false, + visible: true, + dashed: false, + lineWidth: 2.0, + ignoreRayIntersection: true, // this never ray intersects + drawInFront: true + })); + } + } + }; + + this.searchExit = function() { + + // delete all equip hotspots + var i, l = this.equipHotspotOverlays.length; + for (i = 0; i < l; i++) { + Overlays.deleteOverlay(this.equipHotspotOverlays[i]); + } + this.equipHotspotOverlays = []; + }; + this.search = function() { this.grabbedEntity = null; this.isInitialGrab = false; @@ -951,7 +1033,7 @@ function MyController(hand) { var propsForCandidate = Entities.getEntityProperties(candidateEntities[i], GRABBABLE_PROPERTIES); var near = (nearPickedCandidateEntities.indexOf(candidateEntities[i]) >= 0); - var isPhysical = this.propsArePhysical(propsForCandidate); + var isPhysical = propsArePhysical(propsForCandidate); var grabbable; if (isPhysical) { // physical things default to grabbable @@ -1030,7 +1112,7 @@ function MyController(hand) { if ((this.grabbedEntity !== null) && (this.triggerSmoothedGrab() || this.bumperSqueezed())) { // We are squeezing enough to grab, and we've found an entity that we'll try to do something with. var near = (nearPickedCandidateEntities.indexOf(this.grabbedEntity) >= 0) || minDistance <= NEAR_PICK_MAX_DISTANCE; - var isPhysical = this.propsArePhysical(props); + var isPhysical = propsArePhysical(props); // near or far trigger if (grabbableData.wantsTrigger) { @@ -1462,7 +1544,7 @@ function MyController(hand) { } } - var isPhysical = this.propsArePhysical(grabbedProperties) || entityHasActions(this.grabbedEntity); + var isPhysical = propsArePhysical(grabbedProperties) || entityHasActions(this.grabbedEntity); if (isPhysical && this.state == STATE_NEAR_GRABBING) { // grab entity via action if (!this.setupHoldAction()) { @@ -1879,7 +1961,7 @@ function MyController(hand) { var forceVelocity = false; var doSetVelocity = false; - if (parentID != NULL_UUID && deactiveProps.parentID == NULL_UUID && this.propsArePhysical(props)) { + if (parentID != NULL_UUID && deactiveProps.parentID == NULL_UUID && propsArePhysical(props)) { // TODO: EntityScriptingInterface::convertLocationToScriptSemantics should be setting up // props.velocity to be a world-frame velocity and localVelocity to be vs parent. Until that // is done, we use a measured velocity here so that things held via a bumper-grab / parenting-grab From 6d462a477dd3620ddc81efb6355af7df6dd1522e Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 1 Jun 2016 16:54:14 -0700 Subject: [PATCH 15/93] Change equip-hotspot to a green sphere. --- .../system/controllers/handControllerGrab.js | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 0561e73e1f..4163b07c6d 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -897,28 +897,26 @@ function MyController(hand) { var hotspotPos = grabProps.position; - // does this entity have an equip point? + // does this entity have an attach point? var wearableData = getEntityCustomData("wearable", entities[i], undefined); if (wearableData) { var handJointName = this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"; if (wearableData[handJointName]) { - // draw the hotspot around the equip point. + // draw the hotspot around the attach point. hotspotPos = wearableData[handJointName][0]; } } // draw a hotspot! - this.equipHotspotOverlays.push(Overlays.addOverlay("cube", { + this.equipHotspotOverlays.push(Overlays.addOverlay("sphere", { position: hotspotPos, - size: 0.1, + size: 0.2, color: { red: 90, green: 255, blue: 90 }, - alpha: 1, - solid: false, + alpha: 0.7, + solid: true, visible: true, - dashed: false, - lineWidth: 2.0, - ignoreRayIntersection: true, // this never ray intersects - drawInFront: true + ignoreRayIntersection: false, + drawInFront: false })); } } From b836a580ffda8b2625e64ec404d3520cfcf13ed5 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 6 Jun 2016 14:39:14 -0700 Subject: [PATCH 16/93] Updated some debug print information --- scripts/system/controllers/handControllerGrab.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 4163b07c6d..8a96e9d80c 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -376,7 +376,7 @@ function MyController(hand) { print("WARNING: could not find updateMethod for state " + stateToName(this.state)); } } else { - print("WARNING: could not find state " + this.state + " " + CONTROLLER_STATE_MACHINE[this.state]); + print("WARNING: could not find state " + this.state + " in state machine"); } }; @@ -390,7 +390,7 @@ function MyController(hand) { if (WANT_DEBUG || WANT_DEBUG_STATE) { var oldStateName = stateToName(this.state); var newStateName = stateToName(newState); - print("STATE (" + this.hand + "): " + oldStateName + " --> " + newStateName); + print("STATE (" + this.hand + "): " + newStateName + " <-- " + oldStateName); } // exit the old state @@ -401,7 +401,7 @@ function MyController(hand) { exitMethod.call(this); } } else { - print("WARNING: could not find this.state " + this.state); + print("WARNING: could not find state " + this.state + " in state machine"); } // enter the new state @@ -412,7 +412,7 @@ function MyController(hand) { enterMethod.call(this); } } else { - print("WARNING: could not find newState " + newState); + print("WARNING: could not find newState " + newState + " in state machine"); } this.state = newState; @@ -943,6 +943,7 @@ function MyController(hand) { this.setState(STATE_RELEASE); return; } + if (this.state == STATE_HOLD_SEARCHING && this.bumperReleased()) { this.setState(STATE_RELEASE); return; From 74c5ddd49f751937b1814763a18ddc5b189667d1 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Tue, 7 Jun 2016 09:50:10 -0700 Subject: [PATCH 17/93] fix paused interactions with mouse --- scripts/system/away.js | 8 +++++--- scripts/system/controllers/handControllerPointer.js | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/scripts/system/away.js b/scripts/system/away.js index 2b2ea8a42b..399b3dc96c 100644 --- a/scripts/system/away.js +++ b/scripts/system/away.js @@ -176,9 +176,11 @@ function goAway() { // tell the Reticle, we want to stop capturing the mouse until we come back Reticle.allowMouseCapture = false; - if (HMD.active) { - Reticle.visible = false; - } + // Allow users to find their way to other applications, our menus, etc. + // For desktop, that means we want the reticle visible. + // For HMD, the hmd preview will show the system mouse because of allowMouseCapture, + // but we want to turn off our Reticle so that we don't get two in preview and a stuck one in headset. + Reticle.visible = !HMD.active; wasHmdMounted = safeGetHMDMounted(); // always remember the correct state avatarPosition = MyAvatar.position; diff --git a/scripts/system/controllers/handControllerPointer.js b/scripts/system/controllers/handControllerPointer.js index e70f704ce7..7b45babf4d 100644 --- a/scripts/system/controllers/handControllerPointer.js +++ b/scripts/system/controllers/handControllerPointer.js @@ -410,8 +410,8 @@ function update() { if (!Menu.isOptionChecked("First Person")) { return turnOffVisualization(); // What to do? menus can be behind hand! } - if (!Window.hasFocus()) { - return turnOffVisualization(); // Don't mess with other apps + if (!Window.hasFocus() || !Reticle.allowMouseCapture) { + return turnOffVisualization(); // Don't mess with other apps or paused mouse activity } leftTrigger.update(); rightTrigger.update(); From b9ee0c087e6f79b36850799bf476e80ab93c5938 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Tue, 7 Jun 2016 13:45:23 -0700 Subject: [PATCH 18/93] Add a constant hud toggle button to the UI --- interface/resources/icons/hud-01.svg | 105 ++++++++++++++++++++ interface/resources/qml/desktop/Desktop.qml | 2 +- interface/resources/qml/hifi/Desktop.qml | 21 ++++ tests/ui/qml/main.qml | 2 +- 4 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 interface/resources/icons/hud-01.svg diff --git a/interface/resources/icons/hud-01.svg b/interface/resources/icons/hud-01.svg new file mode 100644 index 0000000000..4929389268 --- /dev/null +++ b/interface/resources/icons/hud-01.svg @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/interface/resources/qml/desktop/Desktop.qml b/interface/resources/qml/desktop/Desktop.qml index 241cd91611..e269a63cde 100644 --- a/interface/resources/qml/desktop/Desktop.qml +++ b/interface/resources/qml/desktop/Desktop.qml @@ -299,7 +299,7 @@ FocusScope { if (pinned) { // recalculate our non-pinned children hiddenChildren = d.findMatchingChildren(desktop, function(child){ - return !d.isTopLevelWindow(child) && child.visible; + return !d.isTopLevelWindow(child) && child.visible && !child.pinned; }); hiddenChildren.forEach(function(child){ diff --git a/interface/resources/qml/hifi/Desktop.qml b/interface/resources/qml/hifi/Desktop.qml index 59278a17b4..f1c1aeb4d1 100644 --- a/interface/resources/qml/hifi/Desktop.qml +++ b/interface/resources/qml/hifi/Desktop.qml @@ -47,6 +47,27 @@ Desktop { } } + Item { + id: hudToggleButton + clip: true + width: 50 + height: 50 + anchors.bottom: parent.bottom + anchors.bottomMargin: 32 + anchors.horizontalCenter: parent.horizontalCenter + property bool pinned: true + Image { + y: desktop.pinned ? -50 : 0 + id: hudToggleImage + source: "../../icons/hud-01.svg" + } + MouseArea { + anchors.fill: parent + onClicked: desktop.togglePinned() + } + } + + } diff --git a/tests/ui/qml/main.qml b/tests/ui/qml/main.qml index d752734de4..33408fb821 100644 --- a/tests/ui/qml/main.qml +++ b/tests/ui/qml/main.qml @@ -51,7 +51,7 @@ ApplicationWindow { Button { text: "toggle desktop" - onClicked: desktop.toggleVisible() + onClicked: desktop.togglePinned() } // Error alerts From ba61491ee605d44fd5c39babb134cf85f07e8331 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Tue, 7 Jun 2016 15:33:00 -0700 Subject: [PATCH 19/93] PR feedback --- interface/resources/qml/hifi/Desktop.qml | 22 +++--------- .../resources/qml/hifi/ToggleHudButton.qml | 36 +++++++++++++++++++ 2 files changed, 40 insertions(+), 18 deletions(-) create mode 100644 interface/resources/qml/hifi/ToggleHudButton.qml diff --git a/interface/resources/qml/hifi/Desktop.qml b/interface/resources/qml/hifi/Desktop.qml index f1c1aeb4d1..e127a235e6 100644 --- a/interface/resources/qml/hifi/Desktop.qml +++ b/interface/resources/qml/hifi/Desktop.qml @@ -4,11 +4,13 @@ import QtWebEngine 1.1; import "../desktop" import ".." +import "." Desktop { id: desktop MouseArea { + id: hoverWatch anchors.fill: parent hoverEnabled: true propagateComposedEvents: true @@ -47,28 +49,12 @@ Desktop { } } - Item { - id: hudToggleButton - clip: true - width: 50 - height: 50 + + ToggleHudButton { anchors.bottom: parent.bottom anchors.bottomMargin: 32 anchors.horizontalCenter: parent.horizontalCenter - property bool pinned: true - Image { - y: desktop.pinned ? -50 : 0 - id: hudToggleImage - source: "../../icons/hud-01.svg" - } - MouseArea { - anchors.fill: parent - onClicked: desktop.togglePinned() - } } - - } - diff --git a/interface/resources/qml/hifi/ToggleHudButton.qml b/interface/resources/qml/hifi/ToggleHudButton.qml new file mode 100644 index 0000000000..6454f42305 --- /dev/null +++ b/interface/resources/qml/hifi/ToggleHudButton.qml @@ -0,0 +1,36 @@ +import QtQuick 2.5 +import QtQuick.Controls 1.4 + +import "../windows" + +Window { + //frame: HiddenFrame {} + hideBackground: true + resizable: false + destroyOnCloseButton: false + destroyOnHidden: false + closable: false + shown: true + pinned: true + width: 50 + height: 50 + clip: true + visible: true + + Item { + width: 50 + height: 50 + Image { + y: desktop.pinned ? -50 : 0 + id: hudToggleImage + source: "../../icons/hud-01.svg" + } + MouseArea { + readonly property string overlayMenuItem: "Overlays" + anchors.fill: parent + onClicked: MenuInterface.setIsOptionChecked(overlayMenuItem, !MenuInterface.isOptionChecked(overlayMenuItem)) + } + } +} + + From 29d9f51b584e205d62bf944af6400cf0beebc1a3 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Tue, 7 Jun 2016 16:46:17 -0700 Subject: [PATCH 20/93] Adding missed file --- interface/src/Application.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 8f4d80a86f..c0a7857c95 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1418,7 +1418,7 @@ void Application::initializeUi() { rootContext->setContextProperty("Overlays", &_overlays); rootContext->setContextProperty("Window", DependencyManager::get().data()); - rootContext->setContextProperty("Menu", MenuScriptingInterface::getInstance()); + rootContext->setContextProperty("MenuInterface", MenuScriptingInterface::getInstance()); rootContext->setContextProperty("Stats", Stats::getInstance()); rootContext->setContextProperty("Settings", SettingsScriptingInterface::getInstance()); rootContext->setContextProperty("ScriptDiscoveryService", DependencyManager::get().data()); From eab611acc072d443ab7fccb2dfaed6b4b370046c Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 18 May 2016 21:46:30 -0700 Subject: [PATCH 21/93] Enable Steam VR text input --- libraries/gl/src/gl/OffscreenQmlSurface.cpp | 41 ++++++++++++++++++++ libraries/gl/src/gl/OffscreenQmlSurface.h | 8 +++- plugins/openvr/src/OpenVrDisplayPlugin.cpp | 3 ++ plugins/openvr/src/ViveControllerManager.cpp | 34 +++++++++++++++- 4 files changed, 84 insertions(+), 2 deletions(-) diff --git a/libraries/gl/src/gl/OffscreenQmlSurface.cpp b/libraries/gl/src/gl/OffscreenQmlSurface.cpp index 388ca26482..fa9cb62b7c 100644 --- a/libraries/gl/src/gl/OffscreenQmlSurface.cpp +++ b/libraries/gl/src/gl/OffscreenQmlSurface.cpp @@ -396,6 +396,8 @@ void OffscreenQmlSurface::create(QOpenGLContext* shareContext) { _renderer->_renderControl->_renderWindow = _proxyWindow; + connect(_renderer->_quickWindow, &QQuickWindow::focusObjectChanged, this, &OffscreenQmlSurface::onFocusObjectChanged); + // Create a QML engine. _qmlEngine = new QQmlEngine; if (!_qmlEngine->incubationController()) { @@ -742,3 +744,42 @@ QVariant OffscreenQmlSurface::returnFromUiThread(std::function funct return function(); } + +void OffscreenQmlSurface::onFocusObjectChanged(QObject* object) { + if (!object) { + setFocusText(false); + return; + } + + QVariant result; +#if 1 + auto invokeResult = QMetaObject::invokeMethod(object, "inputMethodQuery", Q_RETURN_ARG(QVariant, result), + Q_ARG(Qt::InputMethodQuery, Qt::ImEnabled), + Q_ARG(QVariant, QVariant())); +#else + + //static const char* INPUT_METHOD_QUERY_METHOD_NAME = "inputMethodQuery(Qt::InputMethodQuery, QVariant)"; + static const char* INPUT_METHOD_QUERY_METHOD_NAME = "inputMethodQuery"; + auto meta = object->metaObject(); + qDebug() << "new focus " << object; + auto index = meta->indexOfMethod(INPUT_METHOD_QUERY_METHOD_NAME); + if (index < 0 || index >= meta->methodCount()) { + setFocusText(false); + return; + } + + auto method = meta->method(index); + auto invokeResult = method.invoke(object, + Q_RETURN_ARG(QVariant, result), + Q_ARG(Qt::InputMethodQuery, Qt::ImEnabled), + Q_ARG(QVariant, QVariant())); +#endif + setFocusText(invokeResult && result.toBool()); +} + +void OffscreenQmlSurface::setFocusText(bool newFocusText) { + if (newFocusText != _focusText) { + _focusText = newFocusText; + emit focusTextChanged(_focusText); + } +} diff --git a/libraries/gl/src/gl/OffscreenQmlSurface.h b/libraries/gl/src/gl/OffscreenQmlSurface.h index 22a1b99fe6..1ce7276877 100644 --- a/libraries/gl/src/gl/OffscreenQmlSurface.h +++ b/libraries/gl/src/gl/OffscreenQmlSurface.h @@ -30,7 +30,7 @@ class OffscreenQmlRenderThread; class OffscreenQmlSurface : public QObject { Q_OBJECT - + Q_PROPERTY(bool focusText READ isFocusText NOTIFY focusTextChanged) public: OffscreenQmlSurface(); virtual ~OffscreenQmlSurface(); @@ -55,6 +55,7 @@ public: _mouseTranslator = mouseTranslator; } + bool isFocusText() const { return _focusText; } void pause(); void resume(); bool isPaused() const; @@ -70,6 +71,8 @@ public: signals: void textureUpdated(unsigned int texture); + void focusObjectChanged(QObject* newFocus); + void focusTextChanged(bool focusText); public slots: void requestUpdate(); @@ -78,6 +81,7 @@ public slots: protected: bool filterEnabled(QObject* originalDestination, QEvent* event) const; + void setFocusText(bool newFocusText); private: QObject* finishQmlLoad(std::function f); @@ -85,6 +89,7 @@ private: private slots: void updateQuick(); + void onFocusObjectChanged(QObject* newFocus); private: friend class OffscreenQmlRenderThread; @@ -97,6 +102,7 @@ private: bool _render{ false }; bool _polish{ true }; bool _paused{ true }; + bool _focusText { false }; uint8_t _maxFps{ 60 }; MouseTranslator _mouseTranslator{ [](const QPointF& p) { return p.toPoint(); } }; QWindow* _proxyWindow { nullptr }; diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index fe406cc29a..fbade9fd68 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -36,12 +36,14 @@ vec3 _trackedDeviceLinearVelocities[vr::k_unMaxTrackedDeviceCount]; vec3 _trackedDeviceAngularVelocities[vr::k_unMaxTrackedDeviceCount]; static mat4 _sensorResetMat; static std::array VR_EYES { { vr::Eye_Left, vr::Eye_Right } }; +bool _openVrDisplayActive { false }; bool OpenVrDisplayPlugin::isSupported() const { return openVrSupported(); } bool OpenVrDisplayPlugin::internalActivate() { + _openVrDisplayActive = true; _container->setIsOptionChecked(StandingHMDSensorMode, true); if (!_system) { @@ -94,6 +96,7 @@ bool OpenVrDisplayPlugin::internalActivate() { void OpenVrDisplayPlugin::internalDeactivate() { Parent::internalDeactivate(); + _openVrDisplayActive = false; _container->setIsOptionChecked(StandingHMDSensorMode, false); if (_system) { // Invalidate poses. It's fine if someone else sets these shared values, but we're about to stop updating them, and diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 739d3cde10..8d6c661ae6 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -12,6 +12,7 @@ #include "ViveControllerManager.h" #include +#include #include #include @@ -22,6 +23,7 @@ #include #include #include +#include #include @@ -55,6 +57,9 @@ bool ViveControllerManager::isSupported() const { return openVrSupported(); } +QMetaObject::Connection _focusConnection; +extern bool _openVrDisplayActive; + bool ViveControllerManager::activate() { InputPlugin::activate(); @@ -67,7 +72,20 @@ bool ViveControllerManager::activate() { _system = acquireOpenVrSystem(); } Q_ASSERT(_system); - + auto offscreenUi = DependencyManager::get(); + _focusConnection = connect(offscreenUi.data(), &OffscreenUi::focusTextChanged, [this](bool focusText) { + if (_openVrDisplayActive) { + auto overlay = vr::VROverlay(); + if (overlay) { + if (focusText) { + //virtual EVROverlayError ShowKeyboard( eInputMode, EGamepadTextInputLineMode eLineInputMode, const char *pchDescription, uint32_t unCharMax, const char *pchExistingText, bool bUseMinimalMode, uint64_t uUserValue) = 0; + overlay->ShowKeyboard(vr::EGamepadTextInputMode::k_EGamepadTextInputModeNormal, vr::k_EGamepadTextInputLineModeSingleLine, "Test", 1024, "", false, 0); + } else { + overlay->HideKeyboard(); + } + } + } + }); // OpenVR provides 3d mesh representations of the controllers // Disabled controller rendering code /* @@ -132,6 +150,8 @@ bool ViveControllerManager::activate() { void ViveControllerManager::deactivate() { InputPlugin::deactivate(); + disconnect(_focusConnection); + _container->removeMenuItem(MENU_NAME, RENDER_CONTROLLERS); _container->removeMenu(MENU_PATH); @@ -220,6 +240,18 @@ void ViveControllerManager::pluginUpdate(float deltaTime, const controller::Inpu return; } + vr::VREvent_t vrEvent; + static char textArray[8192]; + while (vr::VRSystem()->PollNextEvent(&vrEvent, sizeof(vrEvent))) { + if (vrEvent.eventType == vr::VREvent_KeyboardDone) { + auto chars = vr::VROverlay()->GetKeyboardText(textArray, 8192); + QInputMethodEvent* event = new QInputMethodEvent(); + event->setCommitString(QString(QByteArray(textArray, chars)), 0, 0); + auto focusObject = DependencyManager::get()->getWindow()->focusObject(); + qApp->postEvent(focusObject, event); + } + } + // because update mutates the internal state we need to lock userInputMapper->withLock([&, this]() { _inputDevice->update(deltaTime, inputCalibrationData); From c2aa9e7f61f71fd3fd53acf3f9ecdec94d34ce06 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Mon, 6 Jun 2016 15:14:10 -0700 Subject: [PATCH 22/93] Update SteamVR keyboard behavior --- libraries/gl/src/gl/OffscreenQmlSurface.cpp | 27 +------ plugins/openvr/src/OpenVrHelpers.cpp | 85 ++++++++++++++++++++ plugins/openvr/src/OpenVrHelpers.h | 3 + plugins/openvr/src/ViveControllerManager.cpp | 37 +-------- 4 files changed, 95 insertions(+), 57 deletions(-) diff --git a/libraries/gl/src/gl/OffscreenQmlSurface.cpp b/libraries/gl/src/gl/OffscreenQmlSurface.cpp index fa9cb62b7c..14518ac37a 100644 --- a/libraries/gl/src/gl/OffscreenQmlSurface.cpp +++ b/libraries/gl/src/gl/OffscreenQmlSurface.cpp @@ -751,30 +751,9 @@ void OffscreenQmlSurface::onFocusObjectChanged(QObject* object) { return; } - QVariant result; -#if 1 - auto invokeResult = QMetaObject::invokeMethod(object, "inputMethodQuery", Q_RETURN_ARG(QVariant, result), - Q_ARG(Qt::InputMethodQuery, Qt::ImEnabled), - Q_ARG(QVariant, QVariant())); -#else - - //static const char* INPUT_METHOD_QUERY_METHOD_NAME = "inputMethodQuery(Qt::InputMethodQuery, QVariant)"; - static const char* INPUT_METHOD_QUERY_METHOD_NAME = "inputMethodQuery"; - auto meta = object->metaObject(); - qDebug() << "new focus " << object; - auto index = meta->indexOfMethod(INPUT_METHOD_QUERY_METHOD_NAME); - if (index < 0 || index >= meta->methodCount()) { - setFocusText(false); - return; - } - - auto method = meta->method(index); - auto invokeResult = method.invoke(object, - Q_RETURN_ARG(QVariant, result), - Q_ARG(Qt::InputMethodQuery, Qt::ImEnabled), - Q_ARG(QVariant, QVariant())); -#endif - setFocusText(invokeResult && result.toBool()); + QInputMethodQueryEvent query(Qt::ImEnabled); + qApp->sendEvent(object, &query); + setFocusText(query.value(Qt::ImEnabled).toBool()); } void OffscreenQmlSurface::setFocusText(bool newFocusText) { diff --git a/plugins/openvr/src/OpenVrHelpers.cpp b/plugins/openvr/src/OpenVrHelpers.cpp index 155bc9f079..b13a60d388 100644 --- a/plugins/openvr/src/OpenVrHelpers.cpp +++ b/plugins/openvr/src/OpenVrHelpers.cpp @@ -14,9 +14,13 @@ #include #include #include +#include +#include #include +#include + Q_DECLARE_LOGGING_CATEGORY(displayplugins) Q_LOGGING_CATEGORY(displayplugins, "hifi.plugins.display") @@ -90,6 +94,82 @@ void releaseOpenVrSystem() { } } +static char textArray[8192]; + +static QMetaObject::Connection _focusConnection, _focusTextConnection; +extern bool _openVrDisplayActive; +static vr::IVROverlay* _overlay { nullptr }; +static QObject* _focusObject { nullptr }; +static QString _existingText; +static Qt::InputMethodHints _currentHints; + +void showOpenVrKeyboard(bool show = true) { + if (_overlay) { + if (show) { + auto offscreenUi = DependencyManager::get(); + _focusObject = offscreenUi->getWindow()->focusObject(); + + QInputMethodQueryEvent query(Qt::ImQueryInput | Qt::ImHints); + qApp->sendEvent(_focusObject, &query); + _currentHints = Qt::InputMethodHints(query.value(Qt::ImHints).toUInt()); + vr::EGamepadTextInputMode inputMode = vr::k_EGamepadTextInputModeNormal; + if (_currentHints & Qt::ImhHiddenText) { + inputMode = vr::k_EGamepadTextInputModePassword; + } + vr::EGamepadTextInputLineMode lineMode = vr::k_EGamepadTextInputLineModeSingleLine; + if (_currentHints & Qt::ImhMultiLine) { + lineMode = vr::k_EGamepadTextInputLineModeMultipleLines; + } + _existingText = query.value(Qt::ImSurroundingText).toString(); + _overlay->ShowKeyboard(inputMode, lineMode, "Keyboard", 1024, _existingText.toLocal8Bit().toStdString().c_str(), false, (uint64_t)(void*)_focusObject); + } else { + _focusObject = nullptr; + _overlay->HideKeyboard(); + } + } +} + +void finishOpenVrKeyboardInput() { + auto offscreenUi = DependencyManager::get(); + auto chars = _overlay->GetKeyboardText(textArray, 8192); + auto newText = QString(QByteArray(textArray, chars)); + // TODO modify the new text to match the possible input hints: + // ImhDigitsOnly ImhFormattedNumbersOnly ImhUppercaseOnly ImhLowercaseOnly + // ImhDialableCharactersOnly ImhEmailCharactersOnly ImhUrlCharactersOnly ImhLatinOnly + QInputMethodEvent event(_existingText, QList()); + event.setCommitString(newText, 0, _existingText.size()); + qApp->sendEvent(_focusObject, &event); + // Simulate an enter press on the top level window to trigger the action + if (0 == (_currentHints & Qt::ImhMultiLine)) { + qApp->sendEvent(offscreenUi->getWindow(), &QKeyEvent(QEvent::KeyPress, Qt::Key_Return, Qt::KeyboardModifiers(), QString("\n"))); + qApp->sendEvent(offscreenUi->getWindow(), &QKeyEvent(QEvent::KeyRelease, Qt::Key_Return, Qt::KeyboardModifiers())); + } +} + +void enableOpenVrKeyboard() { + auto offscreenUi = DependencyManager::get(); + _overlay = vr::VROverlay(); + + _focusConnection = QObject::connect(offscreenUi->getWindow(), &QQuickWindow::focusObjectChanged, [](QObject* object) { + if (object != _focusObject && _overlay) { + showOpenVrKeyboard(false); + } + }); + + _focusTextConnection = QObject::connect(offscreenUi.data(), &OffscreenUi::focusTextChanged, [](bool focusText) { + if (_openVrDisplayActive) { + showOpenVrKeyboard(focusText); + } + }); +} + + +void disableOpenVrKeyboard() { + QObject::disconnect(_focusTextConnection); + QObject::disconnect(_focusConnection); +} + + void handleOpenVrEvents() { if (!activeHmd) { return; @@ -107,6 +187,10 @@ void handleOpenVrEvents() { activeHmd->AcknowledgeQuit_Exiting(); break; + case vr::VREvent_KeyboardDone: + finishOpenVrKeyboardInput(); + break; + default: break; } @@ -114,3 +198,4 @@ void handleOpenVrEvents() { } } + diff --git a/plugins/openvr/src/OpenVrHelpers.h b/plugins/openvr/src/OpenVrHelpers.h index 1e5914844c..41e7dcb27d 100644 --- a/plugins/openvr/src/OpenVrHelpers.h +++ b/plugins/openvr/src/OpenVrHelpers.h @@ -18,6 +18,9 @@ vr::IVRSystem* acquireOpenVrSystem(); void releaseOpenVrSystem(); void handleOpenVrEvents(); bool openVrQuitRequested(); +void enableOpenVrKeyboard(); +void disableOpenVrKeyboard(); + template void openvr_for_each_eye(F f) { diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 8d6c661ae6..6d3ce46e82 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -11,9 +11,6 @@ #include "ViveControllerManager.h" -#include -#include - #include #include #include @@ -57,9 +54,6 @@ bool ViveControllerManager::isSupported() const { return openVrSupported(); } -QMetaObject::Connection _focusConnection; -extern bool _openVrDisplayActive; - bool ViveControllerManager::activate() { InputPlugin::activate(); @@ -72,20 +66,9 @@ bool ViveControllerManager::activate() { _system = acquireOpenVrSystem(); } Q_ASSERT(_system); - auto offscreenUi = DependencyManager::get(); - _focusConnection = connect(offscreenUi.data(), &OffscreenUi::focusTextChanged, [this](bool focusText) { - if (_openVrDisplayActive) { - auto overlay = vr::VROverlay(); - if (overlay) { - if (focusText) { - //virtual EVROverlayError ShowKeyboard( eInputMode, EGamepadTextInputLineMode eLineInputMode, const char *pchDescription, uint32_t unCharMax, const char *pchExistingText, bool bUseMinimalMode, uint64_t uUserValue) = 0; - overlay->ShowKeyboard(vr::EGamepadTextInputMode::k_EGamepadTextInputModeNormal, vr::k_EGamepadTextInputLineModeSingleLine, "Test", 1024, "", false, 0); - } else { - overlay->HideKeyboard(); - } - } - } - }); + + enableOpenVrKeyboard(); + // OpenVR provides 3d mesh representations of the controllers // Disabled controller rendering code /* @@ -150,7 +133,7 @@ bool ViveControllerManager::activate() { void ViveControllerManager::deactivate() { InputPlugin::deactivate(); - disconnect(_focusConnection); + disableOpenVrKeyboard(); _container->removeMenuItem(MENU_NAME, RENDER_CONTROLLERS); _container->removeMenu(MENU_PATH); @@ -240,18 +223,6 @@ void ViveControllerManager::pluginUpdate(float deltaTime, const controller::Inpu return; } - vr::VREvent_t vrEvent; - static char textArray[8192]; - while (vr::VRSystem()->PollNextEvent(&vrEvent, sizeof(vrEvent))) { - if (vrEvent.eventType == vr::VREvent_KeyboardDone) { - auto chars = vr::VROverlay()->GetKeyboardText(textArray, 8192); - QInputMethodEvent* event = new QInputMethodEvent(); - event->setCommitString(QString(QByteArray(textArray, chars)), 0, 0); - auto focusObject = DependencyManager::get()->getWindow()->focusObject(); - qApp->postEvent(focusObject, event); - } - } - // because update mutates the internal state we need to lock userInputMapper->withLock([&, this]() { _inputDevice->update(deltaTime, inputCalibrationData); From 79c68b2ecb203cb09453e53179eb22e7689d00df Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Tue, 7 Jun 2016 21:49:29 -0700 Subject: [PATCH 23/93] Working on positioning of the keyboard --- plugins/openvr/src/OpenVrHelpers.cpp | 11 ++++++++++- plugins/openvr/src/OpenVrHelpers.h | 10 ++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/plugins/openvr/src/OpenVrHelpers.cpp b/plugins/openvr/src/OpenVrHelpers.cpp index b13a60d388..6533371db8 100644 --- a/plugins/openvr/src/OpenVrHelpers.cpp +++ b/plugins/openvr/src/OpenVrHelpers.cpp @@ -102,6 +102,7 @@ static vr::IVROverlay* _overlay { nullptr }; static QObject* _focusObject { nullptr }; static QString _existingText; static Qt::InputMethodHints _currentHints; +extern vr::TrackedDevicePose_t _trackedDevicePose[vr::k_unMaxTrackedDeviceCount]; void showOpenVrKeyboard(bool show = true) { if (_overlay) { @@ -121,7 +122,15 @@ void showOpenVrKeyboard(bool show = true) { lineMode = vr::k_EGamepadTextInputLineModeMultipleLines; } _existingText = query.value(Qt::ImSurroundingText).toString(); - _overlay->ShowKeyboard(inputMode, lineMode, "Keyboard", 1024, _existingText.toLocal8Bit().toStdString().c_str(), false, (uint64_t)(void*)_focusObject); + + auto showKeyboardResult = _overlay->ShowKeyboard(inputMode, lineMode, "Keyboard", 1024, + _existingText.toLocal8Bit().toStdString().c_str(), false, 0); + + mat4 headPose = toGlm(_trackedDevicePose[0].mDeviceToAbsoluteTracking); + mat4 keyboardTransform = glm::translate(headPose, vec3(0, -0.5, -1)); + keyboardTransform = keyboardTransform * glm::rotate(mat4(), 3.14159f / 4.0f, vec3(-1, 0, 0)); + auto keyboardTransformVr = toOpenVr(keyboardTransform); + _overlay->SetKeyboardTransformAbsolute(vr::ETrackingUniverseOrigin::TrackingUniverseStanding, &keyboardTransformVr); } else { _focusObject = nullptr; _overlay->HideKeyboard(); diff --git a/plugins/openvr/src/OpenVrHelpers.h b/plugins/openvr/src/OpenVrHelpers.h index 41e7dcb27d..426178cd65 100644 --- a/plugins/openvr/src/OpenVrHelpers.h +++ b/plugins/openvr/src/OpenVrHelpers.h @@ -44,3 +44,13 @@ inline mat4 toGlm(const vr::HmdMatrix34_t& m) { m.m[0][3], m.m[1][3], m.m[2][3], 1.0f); return result; } + +inline vr::HmdMatrix34_t toOpenVr(const mat4& m) { + vr::HmdMatrix34_t result; + for (uint8_t i = 0; i < 3; ++i) { + for (uint8_t j = 0; j < 4; ++j) { + result.m[i][j] = m[j][i]; + } + } + return result; +} From 40778d7f299777d5c395e0e742532f214ee5c799 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 8 Jun 2016 10:41:19 -0700 Subject: [PATCH 24/93] Allow env disabling of the keyboard. Show keyboard after short delay to avoid flickers --- plugins/openvr/src/OpenVrHelpers.cpp | 64 ++++++++++++++++++++-------- 1 file changed, 47 insertions(+), 17 deletions(-) diff --git a/plugins/openvr/src/OpenVrHelpers.cpp b/plugins/openvr/src/OpenVrHelpers.cpp index 6533371db8..1ff1c65ef8 100644 --- a/plugins/openvr/src/OpenVrHelpers.cpp +++ b/plugins/openvr/src/OpenVrHelpers.cpp @@ -99,19 +99,33 @@ static char textArray[8192]; static QMetaObject::Connection _focusConnection, _focusTextConnection; extern bool _openVrDisplayActive; static vr::IVROverlay* _overlay { nullptr }; -static QObject* _focusObject { nullptr }; +static QObject* _keyboardFocusObject { nullptr }; static QString _existingText; static Qt::InputMethodHints _currentHints; extern vr::TrackedDevicePose_t _trackedDevicePose[vr::k_unMaxTrackedDeviceCount]; +static bool _keyboardShown { false }; +static const uint32_t SHOW_KEYBOARD_DELAY_MS = 100; void showOpenVrKeyboard(bool show = true) { - if (_overlay) { - if (show) { - auto offscreenUi = DependencyManager::get(); - _focusObject = offscreenUi->getWindow()->focusObject(); + if (!_overlay) { + return; + } - QInputMethodQueryEvent query(Qt::ImQueryInput | Qt::ImHints); - qApp->sendEvent(_focusObject, &query); + if (show) { + // To avoid flickering the keyboard when a text element is only briefly selected, + // show the keyboard asynchrnously after a very short delay, but only after we check + // that the current focus object is still one that is text enabled + QTimer::singleShot(SHOW_KEYBOARD_DELAY_MS, [] { + auto offscreenUi = DependencyManager::get(); + auto currentFocus = offscreenUi->getWindow()->focusObject(); + QInputMethodQueryEvent query(Qt::ImEnabled | Qt::ImQueryInput | Qt::ImHints); + qApp->sendEvent(currentFocus, &query); + // Current focus isn't text enabled, bail early. + if (!query.value(Qt::ImEnabled).toBool()) { + return; + } + // We're going to show the keyboard now... + _keyboardFocusObject = currentFocus; _currentHints = Qt::InputMethodHints(query.value(Qt::ImHints).toUInt()); vr::EGamepadTextInputMode inputMode = vr::k_EGamepadTextInputModeNormal; if (_currentHints & Qt::ImhHiddenText) { @@ -123,17 +137,24 @@ void showOpenVrKeyboard(bool show = true) { } _existingText = query.value(Qt::ImSurroundingText).toString(); - auto showKeyboardResult = _overlay->ShowKeyboard(inputMode, lineMode, "Keyboard", 1024, + auto showKeyboardResult = _overlay->ShowKeyboard(inputMode, lineMode, "Keyboard", 1024, _existingText.toLocal8Bit().toStdString().c_str(), false, 0); - mat4 headPose = toGlm(_trackedDevicePose[0].mDeviceToAbsoluteTracking); - mat4 keyboardTransform = glm::translate(headPose, vec3(0, -0.5, -1)); - keyboardTransform = keyboardTransform * glm::rotate(mat4(), 3.14159f / 4.0f, vec3(-1, 0, 0)); - auto keyboardTransformVr = toOpenVr(keyboardTransform); - _overlay->SetKeyboardTransformAbsolute(vr::ETrackingUniverseOrigin::TrackingUniverseStanding, &keyboardTransformVr); - } else { - _focusObject = nullptr; + if (vr::VROverlayError_None == showKeyboardResult) { + _keyboardShown = true; + // Try to position the keyboard slightly below where the user is looking. + mat4 headPose = toGlm(_trackedDevicePose[0].mDeviceToAbsoluteTracking); + mat4 keyboardTransform = glm::translate(headPose, vec3(0, -0.5, -1)); + keyboardTransform = keyboardTransform * glm::rotate(mat4(), 3.14159f / 4.0f, vec3(-1, 0, 0)); + auto keyboardTransformVr = toOpenVr(keyboardTransform); + _overlay->SetKeyboardTransformAbsolute(vr::ETrackingUniverseOrigin::TrackingUniverseStanding, &keyboardTransformVr); + } + }); + } else { + _keyboardFocusObject = nullptr; + if (_keyboardShown) { _overlay->HideKeyboard(); + _keyboardShown = false; } } } @@ -147,7 +168,7 @@ void finishOpenVrKeyboardInput() { // ImhDialableCharactersOnly ImhEmailCharactersOnly ImhUrlCharactersOnly ImhLatinOnly QInputMethodEvent event(_existingText, QList()); event.setCommitString(newText, 0, _existingText.size()); - qApp->sendEvent(_focusObject, &event); + qApp->sendEvent(_keyboardFocusObject, &event); // Simulate an enter press on the top level window to trigger the action if (0 == (_currentHints & Qt::ImhMultiLine)) { qApp->sendEvent(offscreenUi->getWindow(), &QKeyEvent(QEvent::KeyPress, Qt::Key_Return, Qt::KeyboardModifiers(), QString("\n"))); @@ -155,12 +176,18 @@ void finishOpenVrKeyboardInput() { } } +static const QString DEBUG_FLAG("HIFI_DISABLE_STEAM_VR_KEYBOARD"); +bool disableSteamVrKeyboard = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG); + void enableOpenVrKeyboard() { + if (disableSteamVrKeyboard) { + return; + } auto offscreenUi = DependencyManager::get(); _overlay = vr::VROverlay(); _focusConnection = QObject::connect(offscreenUi->getWindow(), &QQuickWindow::focusObjectChanged, [](QObject* object) { - if (object != _focusObject && _overlay) { + if (object != _keyboardFocusObject) { showOpenVrKeyboard(false); } }); @@ -174,6 +201,9 @@ void enableOpenVrKeyboard() { void disableOpenVrKeyboard() { + if (disableSteamVrKeyboard) { + return; + } QObject::disconnect(_focusTextConnection); QObject::disconnect(_focusConnection); } From f8353fb082c875ec56cb094853cba9730a005610 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 8 Jun 2016 11:11:18 -0700 Subject: [PATCH 25/93] New button content and initial positions. --- .../assets/images/tools/hmd-switch-01.svg | 198 ++++++++++-------- scripts/system/edit.js | 2 +- scripts/system/examples.js | 4 +- scripts/system/goto.js | 2 +- scripts/system/hmd.js | 2 +- 5 files changed, 115 insertions(+), 93 deletions(-) diff --git a/scripts/system/assets/images/tools/hmd-switch-01.svg b/scripts/system/assets/images/tools/hmd-switch-01.svg index 15ecb02b6b..31389d355c 100644 --- a/scripts/system/assets/images/tools/hmd-switch-01.svg +++ b/scripts/system/assets/images/tools/hmd-switch-01.svg @@ -4,99 +4,121 @@ viewBox="0 0 50 150" style="enable-background:new 0 0 50 150;" xml:space="preserve"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + + + + diff --git a/scripts/system/edit.js b/scripts/system/edit.js index afbc679ec4..38d596f83e 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -181,7 +181,7 @@ var toolBar = (function() { function initialize() { toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL, "highfidelity.edit.toolbar", function(windowDimensions, toolbar) { return { - x: windowDimensions.x / 2, + x: (windowDimensions.x / 2) + (Tool.IMAGE_WIDTH * 2), y: windowDimensions.y }; }, { diff --git a/scripts/system/examples.js b/scripts/system/examples.js index 9d33e473af..6f4268182c 100644 --- a/scripts/system/examples.js +++ b/scripts/system/examples.js @@ -60,7 +60,7 @@ var toolBar = (function() { function initialize() { toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL, "highfidelity.examples.toolbar", function(windowDimensions, toolbar) { return { - x: windowDimensions.x / 2, + x: (windowDimensions.x / 2) + (Tool.IMAGE_WIDTH * 2), y: windowDimensions.y }; }, { @@ -135,4 +135,4 @@ var toolBar = (function() { }()); Controller.mousePressEvent.connect(toolBar.mousePressEvent) -Script.scriptEnding.connect(toolBar.cleanup); \ No newline at end of file +Script.scriptEnding.connect(toolBar.cleanup); diff --git a/scripts/system/goto.js b/scripts/system/goto.js index 75d9829905..00b5e912c0 100644 --- a/scripts/system/goto.js +++ b/scripts/system/goto.js @@ -13,7 +13,7 @@ Script.include("libraries/toolBars.js"); function initialPosition(windowDimensions, toolbar) { return { - x: windowDimensions.x / 2 - Tool.IMAGE_WIDTH, + x: (windowDimensions.x / 2) - (Tool.IMAGE_WIDTH * 1), y: windowDimensions.y }; } diff --git a/scripts/system/hmd.js b/scripts/system/hmd.js index 277af68315..8b91e45676 100644 --- a/scripts/system/hmd.js +++ b/scripts/system/hmd.js @@ -22,7 +22,7 @@ var desktopMenuItemName = "Desktop"; function initialPosition(windowDimensions, toolbar) { return { - x: windowDimensions.x / 2 + (2 * Tool.IMAGE_WIDTH), + x: (windowDimensions.x / 2) - (Tool.IMAGE_WIDTH * 2.5), y: windowDimensions.y }; } From 42068cf2dd04bb7e4f4b3e8827f4bafbb4b0c82b Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 8 Jun 2016 15:01:53 -0700 Subject: [PATCH 26/93] Only render equip-hotspots for objects with attach points. --- .../system/controllers/handControllerGrab.js | 32 ++++++++----------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 8a96e9d80c..140aa54144 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -894,30 +894,24 @@ function MyController(hand) { var grabData = getEntityCustomData(GRABBABLE_DATA_KEY, entities[i], undefined); var grabProps = Entities.getEntityProperties(entities[i], GRABBABLE_PROPERTIES); if (grabData) { - - var hotspotPos = grabProps.position; - // does this entity have an attach point? var wearableData = getEntityCustomData("wearable", entities[i], undefined); - if (wearableData) { + if (wearableData && wearableData.joints) { var handJointName = this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"; - if (wearableData[handJointName]) { - // draw the hotspot around the attach point. - hotspotPos = wearableData[handJointName][0]; + if (wearableData.joints[handJointName]) { + // draw the hotspot + this.equipHotspotOverlays.push(Overlays.addOverlay("sphere", { + position: grabProps.position, + size: 0.2, + color: { red: 90, green: 255, blue: 90 }, + alpha: 0.7, + solid: true, + visible: true, + ignoreRayIntersection: false, + drawInFront: false + })); } } - - // draw a hotspot! - this.equipHotspotOverlays.push(Overlays.addOverlay("sphere", { - position: hotspotPos, - size: 0.2, - color: { red: 90, green: 255, blue: 90 }, - alpha: 0.7, - solid: true, - visible: true, - ignoreRayIntersection: false, - drawInFront: false - })); } } }; From 5ef6847dc35b047ffbced088d07de9bcc79bda8e Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 8 Jun 2016 18:36:36 -0700 Subject: [PATCH 27/93] HMD re-centering while driving improvements. Previously the HUD fading in/out would also recenter the hmd sensor and the avatar, which caused many problems including: * The user's view could shift vertically. * Your avatar would briefly go into t-pose * other users would see your avatar go into t-pose. Now we now move the UI sphere instead, which results in a much smoother experience. MyAvatar: added hasDriveInput method. OverlayConductor: * removed avatar and sensor reset, instead the overlay's modelTransform is changed. * revived STANDING mode, which is active if myAvatar->getClearOverlayWhenDriving() is true and you are wearing an HMD. * SITTING & FLAT mode should be unchanged. * Instead of using avatar velocity to fade out/fade in the hud, We use the presense or absanse of avatar drive input. * Additionally, we check distance to the UI sphere, and quickly recenter the hud if the users head is too close to the actual hud sphere. CompositorHelper: * Bug fixes for ray picks not using the modelTransform. HmdDisplayPlugin: * Bug fixes for rendering not using the modelTransform. --- interface/src/avatar/MyAvatar.cpp | 7 +- interface/src/avatar/MyAvatar.h | 2 + interface/src/ui/OverlayConductor.cpp | 113 ++++++++---------- interface/src/ui/OverlayConductor.h | 1 + .../src/display-plugins/CompositorHelper.cpp | 4 +- .../display-plugins/hmd/HmdDisplayPlugin.cpp | 3 +- 6 files changed, 66 insertions(+), 64 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 62772c6933..76e48d34d6 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1261,8 +1261,7 @@ void MyAvatar::prepareForPhysicsSimulation() { _characterController.setPositionAndOrientation(getPosition(), getOrientation()); if (qApp->isHMDMode()) { - bool hasDriveInput = fabsf(_driveKeys[TRANSLATE_X]) > 0.0f || fabsf(_driveKeys[TRANSLATE_Z]) > 0.0f; - _follow.prePhysicsUpdate(*this, deriveBodyFromHMDSensor(), _bodySensorMatrix, hasDriveInput); + _follow.prePhysicsUpdate(*this, deriveBodyFromHMDSensor(), _bodySensorMatrix, hasDriveInput()); } else { _follow.deactivate(); } @@ -2135,3 +2134,7 @@ bool MyAvatar::didTeleport() { lastPosition = pos; return (changeInPosition.length() > MAX_AVATAR_MOVEMENT_PER_FRAME); } + +bool MyAvatar::hasDriveInput() const { + return fabsf(_driveKeys[TRANSLATE_X]) > 0.0f || fabsf(_driveKeys[TRANSLATE_Z]) > 0.0f; +} diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index d3da32e0ed..381d658046 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -264,6 +264,8 @@ public: controller::Pose getLeftHandControllerPoseInAvatarFrame() const; controller::Pose getRightHandControllerPoseInAvatarFrame() const; + bool hasDriveInput() const; + public slots: void increaseSize(); void decreaseSize(); diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index 54dba229e3..c4fd938e97 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -22,10 +22,31 @@ OverlayConductor::OverlayConductor() { OverlayConductor::~OverlayConductor() { } +bool OverlayConductor::shouldCenterUI() const { + + glm::mat4 hmdMat = qApp->getHMDSensorPose(); + glm::vec3 hmdPos = extractTranslation(hmdMat); + glm::vec3 hmdForward = transformVectorFast(hmdMat, glm::vec3(0.0f, 0.0f, -1.0f)); + + Transform uiTransform = qApp->getApplicationCompositor().getModelTransform(); + glm::vec3 uiPos = uiTransform.getTranslation(); + glm::vec3 uiForward = uiTransform.getRotation() * glm::vec3(0.0f, 0.0f, -1.0f); + + const float MAX_COMPOSITOR_DISTANCE = 0.6f; + const float MAX_COMPOSITOR_ANGLE = 180.0f; // rotation check is effectively disabled + if (glm::distance(uiPos, hmdPos) > MAX_COMPOSITOR_DISTANCE || + glm::dot(uiForward, hmdForward) < cosf(glm::radians(MAX_COMPOSITOR_ANGLE))) { + return true; + } + return false; +} + void OverlayConductor::update(float dt) { updateMode(); + MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); + switch (_mode) { case SITTING: { // when sitting, the overlay is at the origin, facing down the -z axis. @@ -36,27 +57,30 @@ void OverlayConductor::update(float dt) { break; } case STANDING: { - // when standing, the overlay is at a reference position, which is set when the overlay is - // enabled. The camera is taken directly from the HMD, but in world space. - // So the sensorToWorldMatrix must be applied. - MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); - Transform t; - t.evalFromRawMatrix(myAvatar->getSensorToWorldMatrix()); - qApp->getApplicationCompositor().setCameraBaseTransform(t); - // detect when head moves out side of sweet spot, or looks away. - mat4 headMat = myAvatar->getSensorToWorldMatrix() * qApp->getHMDSensorPose(); - vec3 headWorldPos = extractTranslation(headMat); - vec3 headForward = glm::quat_cast(headMat) * glm::vec3(0.0f, 0.0f, -1.0f); - Transform modelXform = qApp->getApplicationCompositor().getModelTransform(); - vec3 compositorWorldPos = modelXform.getTranslation(); - vec3 compositorForward = modelXform.getRotation() * glm::vec3(0.0f, 0.0f, -1.0f); - const float MAX_COMPOSITOR_DISTANCE = 0.6f; - const float MAX_COMPOSITOR_ANGLE = 110.0f; - if (_enabled && (glm::distance(headWorldPos, compositorWorldPos) > MAX_COMPOSITOR_DISTANCE || - glm::dot(headForward, compositorForward) < cosf(glm::radians(MAX_COMPOSITOR_ANGLE)))) { - // fade out the overlay - setEnabled(false); + const quint64 REQUIRED_USECS_IN_NEW_MODE_BEFORE_INVISIBLE = 200 * 1000; + const quint64 REQUIRED_USECS_IN_NEW_MODE_BEFORE_VISIBLE = 1000 * 1000; + + // fade in or out the overlay, based on driving. + bool nowDriving = myAvatar->hasDriveInput(); + // Check that we're in this new mode for long enough to really trigger a transition. + if (nowDriving == _driving) { // If there's no change in state, clear any attepted timer. + _timeInPotentialMode = 0; + } else if (_timeInPotentialMode == 0) { // We've just changed with no timer, so start timing now. + _timeInPotentialMode = usecTimestampNow(); + } else if ((usecTimestampNow() - _timeInPotentialMode) > (nowDriving ? REQUIRED_USECS_IN_NEW_MODE_BEFORE_INVISIBLE : REQUIRED_USECS_IN_NEW_MODE_BEFORE_VISIBLE)) { + _timeInPotentialMode = 0; // a real transition + bool wantsOverlays = Menu::getInstance()->isOptionChecked(MenuOption::Overlays); + if (wantsOverlays) { + setEnabled(!nowDriving); + } + _driving = nowDriving; + } + + // center the UI + if (shouldCenterUI()) { + Transform hmdTransform(cancelOutRollAndPitch(qApp->getHMDSensorPose())); + qApp->getApplicationCompositor().setModelTransform(hmdTransform); } break; } @@ -68,43 +92,14 @@ void OverlayConductor::update(float dt) { void OverlayConductor::updateMode() { MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); - if (myAvatar->getClearOverlayWhenDriving()) { - float speed = glm::length(myAvatar->getVelocity()); - const float MIN_DRIVING = 0.2f; - const float MAX_NOT_DRIVING = 0.01f; - const quint64 REQUIRED_USECS_IN_NEW_MODE_BEFORE_INVISIBLE = 200 * 1000; - const quint64 REQUIRED_USECS_IN_NEW_MODE_BEFORE_VISIBLE = 1000 * 1000; - bool nowDriving = _driving; // Assume current _driving mode unless... - if (speed > MIN_DRIVING) { // ... we're definitely moving... - nowDriving = true; - } else if (speed < MAX_NOT_DRIVING) { // ... or definitely not. - nowDriving = false; - } - // Check that we're in this new mode for long enough to really trigger a transition. - if (nowDriving == _driving) { // If there's no change in state, clear any attepted timer. - _timeInPotentialMode = 0; - } else if (_timeInPotentialMode == 0) { // We've just changed with no timer, so start timing now. - _timeInPotentialMode = usecTimestampNow(); - } else if ((usecTimestampNow() - _timeInPotentialMode) > (nowDriving ? REQUIRED_USECS_IN_NEW_MODE_BEFORE_INVISIBLE : REQUIRED_USECS_IN_NEW_MODE_BEFORE_VISIBLE)) { - _timeInPotentialMode = 0; // a real transition - if (nowDriving) { - _wantsOverlays = Menu::getInstance()->isOptionChecked(MenuOption::Overlays); - } else { // reset when coming out of driving - _mode = FLAT; // Seems appropriate to let things reset, below, after the following. - // All reset of, e.g., room-scale location as though by apostrophe key, without all the other adjustments. - qApp->getActiveDisplayPlugin()->resetSensors(); - myAvatar->reset(true, false, false); - } - if (_wantsOverlays) { - setEnabled(!nowDriving); - } - _driving = nowDriving; - } // Else haven't accumulated enough time in new mode, but keep timing. - } Mode newMode; if (qApp->isHMDMode()) { - newMode = SITTING; + if (myAvatar->getClearOverlayWhenDriving()) { + newMode = STANDING; + } else { + newMode = SITTING; + } } else { newMode = FLAT; } @@ -117,11 +112,10 @@ void OverlayConductor::updateMode() { qApp->getApplicationCompositor().setModelTransform(Transform()); break; } - case STANDING: { // STANDING mode is not currently used. + case STANDING: { // enter the STANDING state - // place the overlay at the current hmd position in world space - auto camMat = cancelOutRollAndPitch(myAvatar->getSensorToWorldMatrix() * qApp->getHMDSensorPose()); - qApp->getApplicationCompositor().setModelTransform(Transform(camMat)); + Transform hmdTransform(cancelOutRollAndPitch(qApp->getHMDSensorPose())); + qApp->getApplicationCompositor().setModelTransform(hmdTransform); break; } @@ -132,7 +126,6 @@ void OverlayConductor::updateMode() { } _mode = newMode; - } void OverlayConductor::setEnabled(bool enabled) { @@ -149,7 +142,7 @@ void OverlayConductor::setEnabled(bool enabled) { MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); auto camMat = cancelOutRollAndPitch(myAvatar->getSensorToWorldMatrix() * qApp->getHMDSensorPose()); qApp->getApplicationCompositor().setModelTransform(Transform(camMat)); - } + } } bool OverlayConductor::getEnabled() const { diff --git a/interface/src/ui/OverlayConductor.h b/interface/src/ui/OverlayConductor.h index 1ec66663a4..4ecbac5bcf 100644 --- a/interface/src/ui/OverlayConductor.h +++ b/interface/src/ui/OverlayConductor.h @@ -19,6 +19,7 @@ public: void update(float dt); void setEnabled(bool enable); bool getEnabled() const; + bool shouldCenterUI() const; private: void updateMode(); diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp index d4fff1b976..2d3c79071f 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp @@ -336,7 +336,9 @@ void CompositorHelper::computeHmdPickRay(const glm::vec2& cursorPos, glm::vec3& } glm::mat4 CompositorHelper::getUiTransform() const { - return _currentCamera * glm::inverse(_currentDisplayPlugin->getHeadPose()); + glm::mat4 modelMat; + _modelTransform.getMatrix(modelMat); + return _currentCamera * glm::inverse(_currentDisplayPlugin->getHeadPose()) * modelMat; } //Finds the collision point of a world space ray diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index 1616dcdb77..b9f6ad1a98 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -253,12 +253,13 @@ void HmdDisplayPlugin::compositeScene() { void HmdDisplayPlugin::compositeOverlay() { using namespace oglplus; auto compositorHelper = DependencyManager::get(); + glm::mat4 modelMat = compositorHelper->getModelTransform().getMatrix(); useProgram(_program); _sphereSection->Use(); for_each_eye([&](Eye eye) { eyeViewport(eye); - auto modelView = glm::inverse(_currentPresentFrameInfo.presentPose * getEyeToHeadTransform(eye)); + auto modelView = glm::inverse(_currentPresentFrameInfo.presentPose * getEyeToHeadTransform(eye)) * modelMat; auto mvp = _eyeProjections[eye] * modelView; Uniform(*_program, _mvpUniform).Set(mvp); _sphereSection->Draw(); From 6e3057479ffacde155330039d249b242210adb92 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 9 Jun 2016 11:27:02 -0700 Subject: [PATCH 28/93] fixes for warnings Also overlays menu option should work better in conjunction with ui-center/fading --- interface/src/ui/OverlayConductor.cpp | 4 +--- interface/src/ui/OverlayConductor.h | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index c4fd938e97..9e2e8cb0db 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -71,9 +71,7 @@ void OverlayConductor::update(float dt) { } else if ((usecTimestampNow() - _timeInPotentialMode) > (nowDriving ? REQUIRED_USECS_IN_NEW_MODE_BEFORE_INVISIBLE : REQUIRED_USECS_IN_NEW_MODE_BEFORE_VISIBLE)) { _timeInPotentialMode = 0; // a real transition bool wantsOverlays = Menu::getInstance()->isOptionChecked(MenuOption::Overlays); - if (wantsOverlays) { - setEnabled(!nowDriving); - } + setEnabled(!nowDriving && wantsOverlays); _driving = nowDriving; } diff --git a/interface/src/ui/OverlayConductor.h b/interface/src/ui/OverlayConductor.h index 4ecbac5bcf..2e425454c7 100644 --- a/interface/src/ui/OverlayConductor.h +++ b/interface/src/ui/OverlayConductor.h @@ -34,7 +34,6 @@ private: bool _enabled { false }; bool _driving { false }; quint64 _timeInPotentialMode { 0 }; - bool _wantsOverlays { true }; }; #endif From 685710d0ec088b60b13fe7aef0ba3054cb67155e Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 9 Jun 2016 18:09:04 -0700 Subject: [PATCH 29/93] OverlayConductor improvments * the HUD will fade/in when driving, even in desktop mode. * the HUD no longer pops when you lean outside of the UI sphere, instead it should smoothly fade out and fade back in. * the overlay toggle button should override fading while driving, as expected. * removed any notion of SITTING, STANDING or FLAT mode from overlay. --- interface/src/ui/OverlayConductor.cpp | 139 ++++++++++++-------------- interface/src/ui/OverlayConductor.h | 22 ++-- 2 files changed, 76 insertions(+), 85 deletions(-) diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index 9e2e8cb0db..790652aa0f 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -22,7 +22,7 @@ OverlayConductor::OverlayConductor() { OverlayConductor::~OverlayConductor() { } -bool OverlayConductor::shouldCenterUI() const { +bool OverlayConductor::headOutsideOverlay() const { glm::mat4 hmdMat = qApp->getHMDSensorPose(); glm::vec3 hmdPos = extractTranslation(hmdMat); @@ -41,89 +41,82 @@ bool OverlayConductor::shouldCenterUI() const { return false; } -void OverlayConductor::update(float dt) { - - updateMode(); - +bool OverlayConductor::avatarHasDriveInput() const { MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); - switch (_mode) { - case SITTING: { - // when sitting, the overlay is at the origin, facing down the -z axis. - // the camera is taken directly from the HMD. - Transform identity; - qApp->getApplicationCompositor().setModelTransform(identity); - qApp->getApplicationCompositor().setCameraBaseTransform(identity); - break; - } - case STANDING: { + const quint64 DRIVE_ENABLE_TIME_USECS = 200 * 1000; // 200 ms + const quint64 DRIVE_DISABLE_TIME_USECS = 1000 * 1000; // 1 s - const quint64 REQUIRED_USECS_IN_NEW_MODE_BEFORE_INVISIBLE = 200 * 1000; - const quint64 REQUIRED_USECS_IN_NEW_MODE_BEFORE_VISIBLE = 1000 * 1000; - - // fade in or out the overlay, based on driving. - bool nowDriving = myAvatar->hasDriveInput(); - // Check that we're in this new mode for long enough to really trigger a transition. - if (nowDriving == _driving) { // If there's no change in state, clear any attepted timer. - _timeInPotentialMode = 0; - } else if (_timeInPotentialMode == 0) { // We've just changed with no timer, so start timing now. - _timeInPotentialMode = usecTimestampNow(); - } else if ((usecTimestampNow() - _timeInPotentialMode) > (nowDriving ? REQUIRED_USECS_IN_NEW_MODE_BEFORE_INVISIBLE : REQUIRED_USECS_IN_NEW_MODE_BEFORE_VISIBLE)) { - _timeInPotentialMode = 0; // a real transition - bool wantsOverlays = Menu::getInstance()->isOptionChecked(MenuOption::Overlays); - setEnabled(!nowDriving && wantsOverlays); - _driving = nowDriving; - } - - // center the UI - if (shouldCenterUI()) { - Transform hmdTransform(cancelOutRollAndPitch(qApp->getHMDSensorPose())); - qApp->getApplicationCompositor().setModelTransform(hmdTransform); - } - break; + bool desiredDriving = myAvatar->hasDriveInput(); + if (desiredDriving != _desiredDriving) { + // start timer + _desiredDrivingTimer = usecTimestampNow() + (desiredDriving ? DRIVE_ENABLE_TIME_USECS : DRIVE_DISABLE_TIME_USECS); } - case FLAT: - // do nothing - break; + + _desiredDriving = desiredDriving; + + if (_desiredDrivingTimer != 0 && usecTimestampNow() > _desiredDrivingTimer) { + // timer expired + // change state! + _currentDriving = _desiredDriving; + // disable timer + _desiredDrivingTimer = 0; } + + return _currentDriving; } -void OverlayConductor::updateMode() { +bool OverlayConductor::shouldShowOverlay() const { MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); - Mode newMode; - if (qApp->isHMDMode()) { - if (myAvatar->getClearOverlayWhenDriving()) { - newMode = STANDING; - } else { - newMode = SITTING; - } - } else { - newMode = FLAT; +#ifdef WANT_DEBUG + qDebug() << "AJT: wantsOverlays =" << Menu::getInstance()->isOptionChecked(MenuOption::Overlays) << ", clearOverlayWhenDriving =" << myAvatar->getClearOverlayWhenDriving() << + ", headOutsideOverlay =" << headOutsideOverlay() << ", hasDriveInput =" << avatarHasDriveInput(); +#endif + + return Menu::getInstance()->isOptionChecked(MenuOption::Overlays) && (!myAvatar->getClearOverlayWhenDriving() || (!headOutsideOverlay() && !avatarHasDriveInput())); +} + +bool OverlayConductor::shouldRecenterOnFadeOut() const { + MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); + return Menu::getInstance()->isOptionChecked(MenuOption::Overlays) && myAvatar->getClearOverlayWhenDriving() && headOutsideOverlay(); +} + +void OverlayConductor::centerUI() { + // place the overlay at the current hmd position in sensor space + auto camMat = cancelOutRollAndPitch(qApp->getHMDSensorPose()); + qApp->getApplicationCompositor().setModelTransform(Transform(camMat)); +} + +void OverlayConductor::update(float dt) { + + // centerUI if hmd mode changes + if (qApp->isHMDMode() && !_hmdMode) { + centerUI(); + } + _hmdMode = qApp->isHMDMode(); + + // centerUI if timer expires + if (_fadeOutTime != 0 && usecTimestampNow() > _fadeOutTime) { + // fade out timer expired + _fadeOutTime = 0; + centerUI(); } - if (newMode != _mode) { - switch (newMode) { - case SITTING: { - // enter the SITTING state - // place the overlay at origin - qApp->getApplicationCompositor().setModelTransform(Transform()); - break; - } - case STANDING: { - // enter the STANDING state - Transform hmdTransform(cancelOutRollAndPitch(qApp->getHMDSensorPose())); - qApp->getApplicationCompositor().setModelTransform(hmdTransform); - break; - } + bool showOverlay = shouldShowOverlay(); - case FLAT: - // do nothing - break; + if (showOverlay != getEnabled()) { + if (showOverlay) { + // disable fadeOut timer + _fadeOutTime = 0; + } else if (shouldRecenterOnFadeOut()) { + // start fadeOut timer + const quint64 FADE_OUT_TIME_USECS = 300 * 1000; // 300 ms + _fadeOutTime = usecTimestampNow() + FADE_OUT_TIME_USECS; } } - _mode = newMode; + setEnabled(showOverlay); } void OverlayConductor::setEnabled(bool enabled) { @@ -135,11 +128,9 @@ void OverlayConductor::setEnabled(bool enabled) { auto offscreenUi = DependencyManager::get(); offscreenUi->setPinned(!_enabled); // if the new state is visible/enabled... - if (_enabled && _mode == STANDING) { - // place the overlay at the current hmd position in world space - MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); - auto camMat = cancelOutRollAndPitch(myAvatar->getSensorToWorldMatrix() * qApp->getHMDSensorPose()); - qApp->getApplicationCompositor().setModelTransform(Transform(camMat)); + MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); + if (_enabled && myAvatar->getClearOverlayWhenDriving() && qApp->isHMDMode()) { + centerUI(); } } diff --git a/interface/src/ui/OverlayConductor.h b/interface/src/ui/OverlayConductor.h index 2e425454c7..40ccb4b91f 100644 --- a/interface/src/ui/OverlayConductor.h +++ b/interface/src/ui/OverlayConductor.h @@ -19,21 +19,21 @@ public: void update(float dt); void setEnabled(bool enable); bool getEnabled() const; - bool shouldCenterUI() const; private: - void updateMode(); + bool headOutsideOverlay() const; + bool avatarHasDriveInput() const; + bool shouldShowOverlay() const; + bool shouldRecenterOnFadeOut() const; + void centerUI(); - enum Mode { - FLAT, - SITTING, - STANDING - }; - - Mode _mode { FLAT }; + quint64 _fadeOutTime { 0 }; bool _enabled { false }; - bool _driving { false }; - quint64 _timeInPotentialMode { 0 }; + bool _hmdMode { false }; + + mutable quint64 _desiredDrivingTimer { 0 }; + mutable bool _desiredDriving { false }; + mutable bool _currentDriving { false }; }; #endif From 4344a35c60f421e99e48df11612ac75c1cd21f28 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Fri, 10 Jun 2016 16:00:16 -0700 Subject: [PATCH 30/93] Use the new system pointer functionality. --- .../controllers/handControllerPointer.js | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/scripts/system/controllers/handControllerPointer.js b/scripts/system/controllers/handControllerPointer.js index 7b45babf4d..1728647e5e 100644 --- a/scripts/system/controllers/handControllerPointer.js +++ b/scripts/system/controllers/handControllerPointer.js @@ -305,13 +305,19 @@ var leftTrigger = new Trigger(); var rightTrigger = new Trigger(); var activeTrigger = rightTrigger; var activeHand = Controller.Standard.RightHand; +var LEFT_HUD_LASER = 1; +var RIGHT_HUD_LASER = 2; +var BOTH_HUD_LASERS = LEFT_HUD_LASER + RIGHT_HUD_LASER; +var activeHudLaser = RIGHT_HUD_LASER; function toggleHand() { // unequivocally switch which hand controls mouse position if (activeHand === Controller.Standard.RightHand) { activeHand = Controller.Standard.LeftHand; activeTrigger = leftTrigger; + activeHudLaser = LEFT_HUD_LASER; } else { activeHand = Controller.Standard.RightHand; activeTrigger = rightTrigger; + activeHudLaser = RIGHT_HUD_LASER; } } function makeToggleAction(hand) { // return a function(0|1) that makes the specified hand control mouse when 1 @@ -342,6 +348,7 @@ clickMapping.enable(); // Same properties as handControllerGrab search sphere var BALL_SIZE = 0.011; var BALL_ALPHA = 0.5; +var LASER_COLOR_XYZW = {x: 10 / 255, y: 10 / 255, z: 255 / 255, w: BALL_ALPHA}; var fakeProjectionBall = Overlays.addOverlay("sphere", { size: 5 * BALL_SIZE, color: {red: 255, green: 10, blue: 10}, @@ -356,9 +363,24 @@ Script.scriptEnding.connect(function () { overlays.forEach(Overlays.deleteOverlay); }); var visualizationIsShowing = false; // Not whether it desired, but simply whether it is. Just an optimziation. +var systemLaserOn = false; +var SYSTEM_LASER_DIRECTION = Vec3.normalize({x: 0, y: -1, z: -1}); // Guessing 45 degrees. +function clearSystemLaser() { + if (!systemLaserOn) { + return; + } + print('FIXME remove: disableHandLasers', BOTH_HUD_LASERS); + HMD.disableHandLasers(BOTH_HUD_LASERS); + systemLaserOn = false; +} function turnOffVisualization(optionalEnableClicks) { // because we're showing cursor on HUD if (!optionalEnableClicks) { expireMouseCursor(); + clearSystemLaser(); + } else if (!systemLaserOn) { + print('FIXME remove: setHandLasers', activeHudLaser, true, JSON.stringify(LASER_COLOR_XYZW), JSON.stringify(SYSTEM_LASER_DIRECTION)); + HMD.setHandLasers(activeHudLaser, true, LASER_COLOR_XYZW, SYSTEM_LASER_DIRECTION); + systemLaserOn = true; } if (!visualizationIsShowing) { return; @@ -371,6 +393,7 @@ function turnOffVisualization(optionalEnableClicks) { // because we're showing c var MAX_RAY_SCALE = 32000; // Anything large. It's a scale, not a distance. function updateVisualization(controllerPosition, controllerDirection, hudPosition3d, hudPosition2d) { ignore(controllerPosition, controllerDirection, hudPosition2d); + clearSystemLaser(); // Show an indication of where the cursor will appear when crossing a HUD element, // and where in-world clicking will occur. // From f6ed5a1dae57820a320fa53162960b600bebce47 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 10 Jun 2016 18:33:40 -0700 Subject: [PATCH 31/93] Bugfixes based on feedback * When the overlay is hidden because your head is too close to the sphere, instead of coming back immediately, it waits until the avatar's velocity is near zero for a period of time. * Hooked up jump and fly to MyAvatar::hasDriveInput() * Added an internal state machine to OverlayConductor to manage hiding/showing transitions. * The overlay menu state is now tied directly to the overlay, so it will change state as the overlay is dynamically hidden/shown from code. * Removed slot going directly from MenuOption::Overlays directly to OverlayConductor::setEnable(). --- interface/src/Menu.cpp | 3 +- interface/src/avatar/MyAvatar.cpp | 2 +- interface/src/ui/OverlayConductor.cpp | 139 +++++++++++++++++++------- interface/src/ui/OverlayConductor.h | 35 +++++-- 4 files changed, 131 insertions(+), 48 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 031564fa7a..0c1f2116d9 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -256,8 +256,7 @@ Menu::Menu() { UNSPECIFIED_POSITION, "Advanced"); // View > Overlays - addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Overlays, 0, true, - qApp, SLOT(setOverlaysVisible(bool))); + addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Overlays, 0, true); // Navigate menu ---------------------------------- MenuWrapper* navigateMenu = addMenu("Navigate"); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 76e48d34d6..12fe7c4ac2 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -2136,5 +2136,5 @@ bool MyAvatar::didTeleport() { } bool MyAvatar::hasDriveInput() const { - return fabsf(_driveKeys[TRANSLATE_X]) > 0.0f || fabsf(_driveKeys[TRANSLATE_Z]) > 0.0f; + return fabsf(_driveKeys[TRANSLATE_X]) > 0.0f || fabsf(_driveKeys[TRANSLATE_Y]) > 0.0f || fabsf(_driveKeys[TRANSLATE_Z]) > 0.0f; } diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index 790652aa0f..01649a0b3a 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -17,13 +17,13 @@ #include "OverlayConductor.h" OverlayConductor::OverlayConductor() { + } OverlayConductor::~OverlayConductor() { } bool OverlayConductor::headOutsideOverlay() const { - glm::mat4 hmdMat = qApp->getHMDSensorPose(); glm::vec3 hmdPos = extractTranslation(hmdMat); glm::vec3 hmdForward = transformVectorFast(hmdMat, glm::vec3(0.0f, 0.0f, -1.0f)); @@ -41,7 +41,34 @@ bool OverlayConductor::headOutsideOverlay() const { return false; } -bool OverlayConductor::avatarHasDriveInput() const { +bool OverlayConductor::updateAvatarIsAtRest() { + + MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); + + const quint64 REST_ENABLE_TIME_USECS = 1000 * 1000; // 1 s + const quint64 REST_DISABLE_TIME_USECS = 200 * 1000; // 200 ms + + const float AT_REST_THRESHOLD = 0.01f; + bool desiredAtRest = glm::length(myAvatar->getVelocity()) < AT_REST_THRESHOLD; + if (desiredAtRest != _desiredAtRest) { + // start timer + _desiredAtRestTimer = usecTimestampNow() + (desiredAtRest ? REST_ENABLE_TIME_USECS : REST_DISABLE_TIME_USECS); + } + + _desiredAtRest = desiredAtRest; + + if (_desiredAtRestTimer != 0 && usecTimestampNow() > _desiredAtRestTimer) { + // timer expired + // change state! + _currentAtRest = _desiredAtRest; + // disable timer + _desiredAtRestTimer = 0; + } + + return _currentAtRest; +} + +bool OverlayConductor::updateAvatarHasDriveInput() { MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); const quint64 DRIVE_ENABLE_TIME_USECS = 200 * 1000; // 200 ms @@ -66,57 +93,88 @@ bool OverlayConductor::avatarHasDriveInput() const { return _currentDriving; } -bool OverlayConductor::shouldShowOverlay() const { - MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); - -#ifdef WANT_DEBUG - qDebug() << "AJT: wantsOverlays =" << Menu::getInstance()->isOptionChecked(MenuOption::Overlays) << ", clearOverlayWhenDriving =" << myAvatar->getClearOverlayWhenDriving() << - ", headOutsideOverlay =" << headOutsideOverlay() << ", hasDriveInput =" << avatarHasDriveInput(); -#endif - - return Menu::getInstance()->isOptionChecked(MenuOption::Overlays) && (!myAvatar->getClearOverlayWhenDriving() || (!headOutsideOverlay() && !avatarHasDriveInput())); -} - -bool OverlayConductor::shouldRecenterOnFadeOut() const { - MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); - return Menu::getInstance()->isOptionChecked(MenuOption::Overlays) && myAvatar->getClearOverlayWhenDriving() && headOutsideOverlay(); -} - void OverlayConductor::centerUI() { // place the overlay at the current hmd position in sensor space auto camMat = cancelOutRollAndPitch(qApp->getHMDSensorPose()); qApp->getApplicationCompositor().setModelTransform(Transform(camMat)); } +bool OverlayConductor::userWishesToHide() const { + // user pressed toggle button. + return Menu::getInstance()->isOptionChecked(MenuOption::Overlays) != _prevOverlayMenuChecked && Menu::getInstance()->isOptionChecked(MenuOption::Overlays); +} + +bool OverlayConductor::userWishesToShow() const { + // user pressed toggle button. + return Menu::getInstance()->isOptionChecked(MenuOption::Overlays) != _prevOverlayMenuChecked && !Menu::getInstance()->isOptionChecked(MenuOption::Overlays); +} + +void OverlayConductor::setState(State state) { +#ifdef WANT_DEBUG + static QString stateToString[NumStates] = { "Enabled", "DisabledByDrive", "DisabledByHead", "DisabledByToggle" }; + qDebug() << "OverlayConductor " << stateToString[state] << "<--" << stateToString[_state]; +#endif + _state = state; +} + +OverlayConductor::State OverlayConductor::getState() const { + return _state; +} + void OverlayConductor::update(float dt) { - // centerUI if hmd mode changes + MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); + + // centerUI when hmd mode is first enabled if (qApp->isHMDMode() && !_hmdMode) { centerUI(); } _hmdMode = qApp->isHMDMode(); - // centerUI if timer expires - if (_fadeOutTime != 0 && usecTimestampNow() > _fadeOutTime) { - // fade out timer expired - _fadeOutTime = 0; - centerUI(); + bool prevDriving = _currentDriving; + bool isDriving = updateAvatarHasDriveInput(); + bool drivingChanged = prevDriving != isDriving; + + bool isAtRest = updateAvatarIsAtRest(); + + switch (getState()) { + case Enabled: + if (qApp->isHMDMode() && headOutsideOverlay()) { + setState(DisabledByHead); + setEnabled(false); + } + if (userWishesToHide()) { + setState(DisabledByToggle); + setEnabled(false); + } + if (drivingChanged && isDriving) { + setState(DisabledByDrive); + setEnabled(false); + } + break; + case DisabledByDrive: + if (!isDriving || userWishesToShow()) { + setState(Enabled); + setEnabled(true); + } + break; + case DisabledByHead: + if (isAtRest || userWishesToShow()) { + setState(Enabled); + setEnabled(true); + } + break; + case DisabledByToggle: + if (userWishesToShow()) { + setState(Enabled); + setEnabled(true); + } + break; + default: + break; } - bool showOverlay = shouldShowOverlay(); - - if (showOverlay != getEnabled()) { - if (showOverlay) { - // disable fadeOut timer - _fadeOutTime = 0; - } else if (shouldRecenterOnFadeOut()) { - // start fadeOut timer - const quint64 FADE_OUT_TIME_USECS = 300 * 1000; // 300 ms - _fadeOutTime = usecTimestampNow() + FADE_OUT_TIME_USECS; - } - } - - setEnabled(showOverlay); + _prevOverlayMenuChecked = Menu::getInstance()->isOptionChecked(MenuOption::Overlays); } void OverlayConductor::setEnabled(bool enabled) { @@ -127,6 +185,11 @@ void OverlayConductor::setEnabled(bool enabled) { _enabled = enabled; // set the new value auto offscreenUi = DependencyManager::get(); offscreenUi->setPinned(!_enabled); + + // ensure that the the state of the menu item reflects the state of the overlay. + Menu::getInstance()->setIsOptionChecked(MenuOption::Overlays, _enabled); + _prevOverlayMenuChecked = _enabled; + // if the new state is visible/enabled... MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); if (_enabled && myAvatar->getClearOverlayWhenDriving() && qApp->isHMDMode()) { diff --git a/interface/src/ui/OverlayConductor.h b/interface/src/ui/OverlayConductor.h index 40ccb4b91f..83d6957012 100644 --- a/interface/src/ui/OverlayConductor.h +++ b/interface/src/ui/OverlayConductor.h @@ -22,18 +22,39 @@ public: private: bool headOutsideOverlay() const; - bool avatarHasDriveInput() const; - bool shouldShowOverlay() const; - bool shouldRecenterOnFadeOut() const; + bool updateAvatarHasDriveInput(); + bool updateAvatarIsAtRest(); + bool userWishesToHide() const; + bool userWishesToShow() const; void centerUI(); - quint64 _fadeOutTime { 0 }; + enum State { + Enabled = 0, + DisabledByDrive, + DisabledByHead, + DisabledByToggle, + NumStates + }; + + void setState(State state); + State getState() const; + + State _state { DisabledByDrive }; + + bool _prevOverlayMenuChecked { true }; bool _enabled { false }; bool _hmdMode { false }; + bool _disabledFromHead { false }; - mutable quint64 _desiredDrivingTimer { 0 }; - mutable bool _desiredDriving { false }; - mutable bool _currentDriving { false }; + // used by updateAvatarHasDriveInput + quint64 _desiredDrivingTimer { 0 }; + bool _desiredDriving { false }; + bool _currentDriving { false }; + + // used by updateAvatarIsAtRest + quint64 _desiredAtRestTimer { 0 }; + bool _desiredAtRest { true }; + bool _currentAtRest { true }; }; #endif From ab4bef7d55cbef9063ad0a53e85d5353e5fb66eb Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 13 Jun 2016 11:19:47 -0700 Subject: [PATCH 32/93] Fixes based on PR feedback * The "Clear Overlay When Driving" avatar preference is obeyed. * sensor reset will also center the ui. --- interface/src/Application.cpp | 1 + interface/src/ui/OverlayConductor.cpp | 6 +++--- interface/src/ui/OverlayConductor.h | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 1e6f7ba995..e494142302 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4209,6 +4209,7 @@ void Application::resetSensors(bool andReload) { DependencyManager::get()->reset(); DependencyManager::get()->reset(); getActiveDisplayPlugin()->resetSensors(); + _overlayConductor.centerUI(); getMyAvatar()->reset(andReload); QMetaObject::invokeMethod(DependencyManager::get().data(), "reset", Qt::QueuedConnection); } diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index 01649a0b3a..b653d0f445 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -139,7 +139,7 @@ void OverlayConductor::update(float dt) { switch (getState()) { case Enabled: - if (qApp->isHMDMode() && headOutsideOverlay()) { + if (myAvatar->getClearOverlayWhenDriving() && qApp->isHMDMode() && headOutsideOverlay()) { setState(DisabledByHead); setEnabled(false); } @@ -147,7 +147,7 @@ void OverlayConductor::update(float dt) { setState(DisabledByToggle); setEnabled(false); } - if (drivingChanged && isDriving) { + if (myAvatar->getClearOverlayWhenDriving() && drivingChanged && isDriving) { setState(DisabledByDrive); setEnabled(false); } @@ -192,7 +192,7 @@ void OverlayConductor::setEnabled(bool enabled) { // if the new state is visible/enabled... MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); - if (_enabled && myAvatar->getClearOverlayWhenDriving() && qApp->isHMDMode()) { + if (_enabled && qApp->isHMDMode()) { centerUI(); } } diff --git a/interface/src/ui/OverlayConductor.h b/interface/src/ui/OverlayConductor.h index 83d6957012..fcfdac72a5 100644 --- a/interface/src/ui/OverlayConductor.h +++ b/interface/src/ui/OverlayConductor.h @@ -20,13 +20,14 @@ public: void setEnabled(bool enable); bool getEnabled() const; + void centerUI(); + private: bool headOutsideOverlay() const; bool updateAvatarHasDriveInput(); bool updateAvatarIsAtRest(); bool userWishesToHide() const; bool userWishesToShow() const; - void centerUI(); enum State { Enabled = 0, From 1ca1f98034731127e00954405b8c8df5e8a36aff Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 13 Jun 2016 12:01:42 -0700 Subject: [PATCH 33/93] Fix unused variable warning --- interface/src/ui/OverlayConductor.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index b653d0f445..a897d85472 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -191,7 +191,6 @@ void OverlayConductor::setEnabled(bool enabled) { _prevOverlayMenuChecked = _enabled; // if the new state is visible/enabled... - MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); if (_enabled && qApp->isHMDMode()) { centerUI(); } From 21e67fc4d844c8811231029fbb6fe60e24ac5dfc Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 13 Jun 2016 12:03:42 -0700 Subject: [PATCH 34/93] Another unused variable warning --- interface/src/ui/OverlayConductor.h | 1 - 1 file changed, 1 deletion(-) diff --git a/interface/src/ui/OverlayConductor.h b/interface/src/ui/OverlayConductor.h index fcfdac72a5..375b2652f6 100644 --- a/interface/src/ui/OverlayConductor.h +++ b/interface/src/ui/OverlayConductor.h @@ -45,7 +45,6 @@ private: bool _prevOverlayMenuChecked { true }; bool _enabled { false }; bool _hmdMode { false }; - bool _disabledFromHead { false }; // used by updateAvatarHasDriveInput quint64 _desiredDrivingTimer { 0 }; From 9640727f518e06ad915746848ad3b8ebea030d7e Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 8 Jun 2016 17:09:30 -0700 Subject: [PATCH 35/93] Hand laser rendering support in HMD plugins. --- .../src/scripting/HMDScriptingInterface.cpp | 10 ++ .../src/scripting/HMDScriptingInterface.h | 2 + .../display-plugins/OpenGLDisplayPlugin.cpp | 53 ++++--- .../src/display-plugins/OpenGLDisplayPlugin.h | 21 ++- .../display-plugins/hmd/HmdDisplayPlugin.cpp | 149 +++++++++++++++++- .../display-plugins/hmd/HmdDisplayPlugin.h | 20 ++- libraries/gl/src/gl/OglplusHelpers.cpp | 92 ++++++++++- libraries/gl/src/gl/OglplusHelpers.h | 5 +- libraries/plugins/src/plugins/DisplayPlugin.h | 20 +++ .../oculus/src/OculusBaseDisplayPlugin.cpp | 5 +- .../src/OculusLegacyDisplayPlugin.cpp | 5 +- plugins/openvr/src/OpenVrDisplayPlugin.cpp | 63 ++++++-- plugins/openvr/src/OpenVrDisplayPlugin.h | 1 - plugins/openvr/src/OpenVrHelpers.cpp | 4 + plugins/openvr/src/OpenVrHelpers.h | 1 + 15 files changed, 399 insertions(+), 52 deletions(-) diff --git a/interface/src/scripting/HMDScriptingInterface.cpp b/interface/src/scripting/HMDScriptingInterface.cpp index 7bf1547a3c..02840a9775 100644 --- a/interface/src/scripting/HMDScriptingInterface.cpp +++ b/interface/src/scripting/HMDScriptingInterface.cpp @@ -105,3 +105,13 @@ QString HMDScriptingInterface::preferredAudioInput() const { QString HMDScriptingInterface::preferredAudioOutput() const { return qApp->getActiveDisplayPlugin()->getPreferredAudioOutDevice(); } + +bool HMDScriptingInterface::setHandLasers(int hands, bool enabled, const glm::vec4& color, const glm::vec3& direction) const { + return qApp->getActiveDisplayPlugin()->setHandLaser(hands, + enabled ? DisplayPlugin::HandLaserMode::Overlay : DisplayPlugin::HandLaserMode::None, + color, direction); +} + +void HMDScriptingInterface::disableHandLasers(int hands) const { + qApp->getActiveDisplayPlugin()->setHandLaser(hands, DisplayPlugin::HandLaserMode::None); +} diff --git a/interface/src/scripting/HMDScriptingInterface.h b/interface/src/scripting/HMDScriptingInterface.h index d4c7b7cc0e..c55320ca83 100644 --- a/interface/src/scripting/HMDScriptingInterface.h +++ b/interface/src/scripting/HMDScriptingInterface.h @@ -36,6 +36,8 @@ public: Q_INVOKABLE glm::vec2 overlayToSpherical(const glm::vec2 & overlayPos) const; Q_INVOKABLE QString preferredAudioInput() const; Q_INVOKABLE QString preferredAudioOutput() const; + Q_INVOKABLE bool setHandLasers(int hands, bool enabled, const glm::vec4& color, const glm::vec3& direction) const; + Q_INVOKABLE void disableHandLasers(int hands) const; public: HMDScriptingInterface(); diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index d9ee979777..363bde15e6 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -213,9 +213,10 @@ OpenGLDisplayPlugin::OpenGLDisplayPlugin() { } void OpenGLDisplayPlugin::cleanupForSceneTexture(const gpu::TexturePointer& sceneTexture) { - Lock lock(_mutex); - Q_ASSERT(_sceneTextureToFrameIndexMap.contains(sceneTexture)); - _sceneTextureToFrameIndexMap.remove(sceneTexture); + withRenderThreadLock([&] { + Q_ASSERT(_sceneTextureToFrameIndexMap.contains(sceneTexture)); + _sceneTextureToFrameIndexMap.remove(sceneTexture); + }); } @@ -394,10 +395,9 @@ void OpenGLDisplayPlugin::submitSceneTexture(uint32_t frameIndex, const gpu::Tex return; } - { - Lock lock(_mutex); + withRenderThreadLock([&] { _sceneTextureToFrameIndexMap[sceneTexture] = frameIndex; - } + }); // Submit it to the presentation thread via escrow _sceneTextureEscrow.submit(sceneTexture); @@ -431,11 +431,12 @@ void OpenGLDisplayPlugin::updateTextures() { } void OpenGLDisplayPlugin::updateFrameData() { - Lock lock(_mutex); - auto previousFrameIndex = _currentPresentFrameIndex; - _currentPresentFrameIndex = _sceneTextureToFrameIndexMap[_currentSceneTexture]; - auto skippedCount = (_currentPresentFrameIndex - previousFrameIndex) - 1; - _droppedFrameRate.increment(skippedCount); + withPresentThreadLock([&] { + auto previousFrameIndex = _currentPresentFrameIndex; + _currentPresentFrameIndex = _sceneTextureToFrameIndexMap[_currentSceneTexture]; + auto skippedCount = (_currentPresentFrameIndex - previousFrameIndex) - 1; + _droppedFrameRate.increment(skippedCount); + }); } void OpenGLDisplayPlugin::compositeOverlay() { @@ -492,14 +493,14 @@ void OpenGLDisplayPlugin::compositeLayers() { } _compositeFramebuffer->Bound(Framebuffer::Target::Draw, [&] { Context::Viewport(targetRenderSize.x, targetRenderSize.y); - Context::Clear().DepthBuffer(); - glBindTexture(GL_TEXTURE_2D, getSceneTextureId()); - compositeScene(); + auto sceneTextureId = getSceneTextureId(); auto overlayTextureId = getOverlayTextureId(); + glBindTexture(GL_TEXTURE_2D, sceneTextureId); + compositeScene(); if (overlayTextureId) { - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBindTexture(GL_TEXTURE_2D, overlayTextureId); + Context::Enable(Capability::Blend); + Context::BlendFunc(BlendFunction::SrcAlpha, BlendFunction::OneMinusSrcAlpha); compositeOverlay(); auto compositorHelper = DependencyManager::get(); @@ -507,10 +508,14 @@ void OpenGLDisplayPlugin::compositeLayers() { auto& cursorManager = Cursor::Manager::instance(); const auto& cursorData = _cursorsData[cursorManager.getCursor()->getIcon()]; glBindTexture(GL_TEXTURE_2D, cursorData.texture); + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, overlayTextureId); compositePointer(); + glBindTexture(GL_TEXTURE_2D, 0); + glActiveTexture(GL_TEXTURE0); } glBindTexture(GL_TEXTURE_2D, 0); - glDisable(GL_BLEND); + Context::Disable(Capability::Blend); } }); } @@ -549,7 +554,11 @@ float OpenGLDisplayPlugin::newFramePresentRate() const { } float OpenGLDisplayPlugin::droppedFrameRate() const { - return _droppedFrameRate.rate(); + float result; + withRenderThreadLock([&] { + result = _droppedFrameRate.rate(); + }); + return result; } float OpenGLDisplayPlugin::presentRate() const { @@ -664,3 +673,11 @@ void OpenGLDisplayPlugin::useProgram(const ProgramPtr& program) { _activeProgram = program; } } + +void OpenGLDisplayPlugin::assertIsRenderThread() const { + Q_ASSERT(QThread::currentThread() != _presentThread); +} + +void OpenGLDisplayPlugin::assertIsPresentThread() const { + Q_ASSERT(QThread::currentThread() == _presentThread); +} diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h index c87ff1bc93..ec8aa45840 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -109,7 +109,6 @@ protected: int32_t _alphaUniform { -1 }; ShapeWrapperPtr _plane; - mutable Mutex _mutex; RateCounter<> _droppedFrameRate; RateCounter<> _newFrameRate; RateCounter<> _presentRate; @@ -135,7 +134,27 @@ protected: BasicFramebufferWrapperPtr _compositeFramebuffer; bool _lockCurrentTexture { false }; + void assertIsRenderThread() const; + void assertIsPresentThread() const; + + template + void withPresentThreadLock(F f) const { + assertIsPresentThread(); + Lock lock(_presentMutex); + f(); + } + + template + void withRenderThreadLock(F f) const { + assertIsRenderThread(); + Lock lock(_presentMutex); + f(); + } + private: + // Any resource shared by the main thread and the presntaion thread must + // be serialized through this mutex + mutable Mutex _presentMutex; ProgramPtr _activeProgram; }; diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index 1616dcdb77..ce1a42971e 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -37,7 +38,6 @@ QRect HmdDisplayPlugin::getRecommendedOverlayRect() const { return CompositorHelper::VIRTUAL_SCREEN_RECOMMENDED_OVERLAY_RECT; } - bool HmdDisplayPlugin::internalActivate() { _monoPreview = _container->getBoolSetting("monoPreview", DEFAULT_MONO_VIEW); @@ -197,14 +197,43 @@ static ProgramPtr getReprojectionProgram() { #endif +static const char * LASER_VS = R"VS(#version 410 core +uniform mat4 mvp = mat4(1); + +in vec3 Position; + +out vec3 vPosition; + +void main() { + gl_Position = mvp * vec4(Position, 1); + vPosition = Position; +} + +)VS"; + +static const char * LASER_FS = R"FS(#version 410 core + +uniform vec4 color = vec4(1.0, 1.0, 1.0, 1.0); +in vec3 vPosition; + +out vec4 FragColor; + +void main() { + FragColor = color; +} + +)FS"; + void HmdDisplayPlugin::customizeContext() { Parent::customizeContext(); // Only enable mirroring if we know vsync is disabled enableVsync(false); _enablePreview = !isVsyncEnabled(); _sphereSection = loadSphereSection(_program, CompositorHelper::VIRTUAL_UI_TARGET_FOV.y, CompositorHelper::VIRTUAL_UI_ASPECT_RATIO); + compileProgram(_laserProgram, LASER_VS, LASER_FS); + _laserGeometry = loadLaser(_laserProgram); compileProgram(_reprojectionProgram, REPROJECTION_VS, REPROJECTION_FS); - + using namespace oglplus; REPROJECTION_MATRIX_LOCATION = Uniform(*_reprojectionProgram, "reprojection").Location(); INVERSE_PROJECTION_MATRIX_LOCATION = Uniform(*_reprojectionProgram, "inverseProjections").Location(); @@ -215,6 +244,8 @@ void HmdDisplayPlugin::uncustomizeContext() { _sphereSection.reset(); _compositeFramebuffer.reset(); _reprojectionProgram.reset(); + _laserProgram.reset(); + _laserGeometry.reset(); Parent::uncustomizeContext(); } @@ -285,6 +316,8 @@ void HmdDisplayPlugin::compositePointer() { Uniform(*_program, _mvpUniform).Set(mvp); _plane->Draw(); }); + + compositeLasers(); } void HmdDisplayPlugin::internalPresent() { @@ -343,22 +376,122 @@ void HmdDisplayPlugin::setEyeRenderPose(uint32_t frameIndex, Eye eye, const glm: void HmdDisplayPlugin::updateFrameData() { // Check if we have old frame data to discard - { - Lock lock(_mutex); + withPresentThreadLock([&] { auto itr = _frameInfos.find(_currentPresentFrameIndex); if (itr != _frameInfos.end()) { _frameInfos.erase(itr); } - } + }); Parent::updateFrameData(); - { - Lock lock(_mutex); + withPresentThreadLock([&] { _currentPresentFrameInfo = _frameInfos[_currentPresentFrameIndex]; - } + }); } glm::mat4 HmdDisplayPlugin::getHeadPose() const { return _currentRenderFrameInfo.renderPose; } + +bool HmdDisplayPlugin::setHandLaser(uint32_t hands, HandLaserMode mode, const vec4& color, const vec3& direction) { + HandLaserInfo info; + info.mode = mode; + info.color = color; + info.direction = direction; + withRenderThreadLock([&] { + if (hands & Hand::LeftHand) { + _handLasers[0] = info; + } + if (hands & Hand::RightHand) { + _handLasers[1] = info; + } + }); + // FIXME defer to a child class plugin to determine if hand lasers are actually + return true; +} + +static float calculateRayUiCollisionDistance(const glm::mat4& headPose, const glm::vec3& position, const glm::vec3& direction) { + auto relativePosition4 = glm::inverse(headPose) * vec4(position, 1); + auto relativePosition = vec3(relativePosition4) / relativePosition4.w; + auto relativeDirection = glm::inverse(glm::quat_cast(headPose)) * direction; + if (glm::abs(glm::length2(relativeDirection) - 1.0f) > EPSILON) { + relativeDirection = glm::normalize(relativeDirection); + } + float uiRadius = 1.0f; + float instersectionDistance; + if (!glm::intersectRaySphere(relativePosition, relativeDirection, vec3(0), uiRadius * uiRadius, instersectionDistance)) { + return -1; + } + return instersectionDistance; +} + +void HmdDisplayPlugin::compositeLasers() { + std::array handLasers; + std::array renderHandPoses; + withPresentThreadLock([&] { + handLasers = _handLasers; + renderHandPoses = _handPoses; + }); + + // If neither hand laser is activated, exit + if (!handLasers[0].valid() && !handLasers[1].valid()) { + return; + } + + static const glm::mat4 identity; + if (renderHandPoses[0] == identity && renderHandPoses[1] == identity) { + return; + } + + // Render hand lasers + using namespace oglplus; + useProgram(_laserProgram); + _laserGeometry->Use(); + std::array handLaserModelMatrices; + + for (int i = 0; i < 2; ++i) { + if (renderHandPoses[i] == identity) { + continue; + } + const auto& handLaser = handLasers[i]; + if (!handLaser.valid()) { + continue; + } + + const auto& laserDirection = handLaser.direction; + auto model = renderHandPoses[i]; + auto castDirection = glm::quat_cast(model) * laserDirection; + + // Find the intersection of the laser with he UI and use it to scale the model matrix + float distance = calculateRayUiCollisionDistance(_currentPresentFrameInfo.presentPose, vec3(renderHandPoses[i][3]), castDirection); + if (distance < 0) { + continue; + } + + // Make sure we rotate to match the desired laser direction + if (laserDirection != Vectors::UNIT_NEG_Z) { + auto rotation = glm::rotation(Vectors::UNIT_NEG_Z, laserDirection); + model = model * glm::mat4_cast(rotation); + } + + model = glm::scale(model, vec3(distance)); + handLaserModelMatrices[i] = model; + } + + for_each_eye([&](Eye eye) { + eyeViewport(eye); + auto eyePose = _currentPresentFrameInfo.presentPose * getEyeToHeadTransform(eye); + auto view = glm::inverse(eyePose); + const auto& projection = _eyeProjections[eye]; + for (int i = 0; i < 2; ++i) { + if (handLaserModelMatrices[i] == identity) { + continue; + } + Uniform(*_laserProgram, "mvp").Set(projection * view * handLaserModelMatrices[i]); + Uniform(*_laserProgram, "color").Set(handLasers[i].color); + _laserGeometry->Draw(); + // TODO render some kind of visual indicator at the intersection point with the UI. + } + }); +} diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h index e6ceb7e376..7cdcf06e9a 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h @@ -30,7 +30,7 @@ public: virtual glm::mat4 getHeadPose() const override; - + bool setHandLaser(uint32_t hands, HandLaserMode mode, const vec4& color, const vec3& direction) override; protected: virtual void hmdPresent() = 0; @@ -47,6 +47,22 @@ protected: void uncustomizeContext() override; void updateFrameData() override; + void compositeLasers(); + + + struct HandLaserInfo { + HandLaserMode mode { HandLaserMode::None }; + vec4 color { 1.0f }; + vec3 direction { 0, 0, -1 }; + + // Is this hand laser info suitable for drawing? + bool valid() const { + return (mode != HandLaserMode::None && color.a > 0.0f && direction != vec3()); + } + }; + + std::array _handLasers; + std::array _handPoses; std::array _eyeOffsets; std::array _eyeProjections; std::array _eyeInverseProjections; @@ -75,5 +91,7 @@ private: bool _enableReprojection { true }; ShapeWrapperPtr _sphereSection; ProgramPtr _reprojectionProgram; + ProgramPtr _laserProgram; + ShapeWrapperPtr _laserGeometry; }; diff --git a/libraries/gl/src/gl/OglplusHelpers.cpp b/libraries/gl/src/gl/OglplusHelpers.cpp index 5bf0298593..7a535a806d 100644 --- a/libraries/gl/src/gl/OglplusHelpers.cpp +++ b/libraries/gl/src/gl/OglplusHelpers.cpp @@ -45,9 +45,11 @@ in vec2 vTexCoord; out vec4 FragColor; void main() { - FragColor = texture(sampler, vTexCoord); FragColor.a *= alpha; + if (FragColor.a <= 0.0) { + discard; + } } )FS"; @@ -359,6 +361,94 @@ ShapeWrapperPtr loadSphereSection(ProgramPtr program, float fov, float aspect, i ); } +namespace oglplus { + namespace shapes { + + class Laser : public DrawingInstructionWriter, public DrawMode { + public: + using IndexArray = std::vector; + using PosArray = std::vector; + /// The type of the index container returned by Indices() + // vertex positions + PosArray _pos_data; + IndexArray _idx_data; + unsigned int _prim_count { 0 }; + + public: + Laser() { + int vertices = 2; + _pos_data.resize(vertices * 3); + _pos_data[0] = 0; + _pos_data[1] = 0; + _pos_data[2] = 0; + + _pos_data[3] = 0; + _pos_data[4] = 0; + _pos_data[5] = -1; + + _idx_data.push_back(0); + _idx_data.push_back(1); + _prim_count = 1; + } + + /// Returns the winding direction of faces + FaceOrientation FaceWinding(void) const { + return FaceOrientation::CCW; + } + + /// Queries the bounding sphere coordinates and dimensions + template + void BoundingSphere(Sphere& bounding_sphere) const { + bounding_sphere = Sphere(0, 0, -0.5, 0.5); + } + + typedef GLuint(Laser::*VertexAttribFunc)(std::vector&) const; + + /// Makes the vertex positions and returns the number of values per vertex + template + GLuint Positions(std::vector& dest) const { + dest.clear(); + dest.insert(dest.begin(), _pos_data.begin(), _pos_data.end()); + return 3; + } + + typedef VertexAttribsInfo< + Laser, + std::tuple + > VertexAttribs; + + + /// Returns element indices that are used with the drawing instructions + const IndexArray & Indices(Default = Default()) const { + return _idx_data; + } + + /// Returns the instructions for rendering of faces + DrawingInstructions Instructions(PrimitiveType primitive) const { + DrawingInstructions instr = MakeInstructions(); + DrawOperation operation; + operation.method = DrawOperation::Method::DrawElements; + operation.mode = primitive; + operation.first = 0; + operation.count = _prim_count * 3; + operation.restart_index = DrawOperation::NoRestartIndex(); + operation.phase = 0; + AddInstruction(instr, operation); + return instr; + } + + /// Returns the instructions for rendering of faces + DrawingInstructions Instructions(Default = Default()) const { + return Instructions(PrimitiveType::Lines); + } + }; + } +} + +ShapeWrapperPtr loadLaser(const ProgramPtr& program) { + return std::make_shared(shapes::ShapeWrapper("Position", shapes::Laser(), *program)); +} + void TextureRecycler::setSize(const uvec2& size) { if (size == _size) { return; diff --git a/libraries/gl/src/gl/OglplusHelpers.h b/libraries/gl/src/gl/OglplusHelpers.h index afb06069b8..8940205b21 100644 --- a/libraries/gl/src/gl/OglplusHelpers.h +++ b/libraries/gl/src/gl/OglplusHelpers.h @@ -64,8 +64,9 @@ ProgramPtr loadCubemapShader(); void compileProgram(ProgramPtr & result, const std::string& vs, const std::string& fs); ShapeWrapperPtr loadSkybox(ProgramPtr program); ShapeWrapperPtr loadPlane(ProgramPtr program, float aspect = 1.0f); -ShapeWrapperPtr loadSphereSection(ProgramPtr program, float fov = PI / 3.0f * 2.0f, float aspect = 16.0f / 9.0f, int slices = 32, int stacks = 32); - +ShapeWrapperPtr loadSphereSection(ProgramPtr program, float fov = PI / 3.0f * 2.0f, float aspect = 16.0f / 9.0f, int slices = 128, int stacks = 128); +ShapeWrapperPtr loadLaser(const ProgramPtr& program); + // A basic wrapper for constructing a framebuffer with a renderbuffer // for the depth attachment and an undefined type for the color attachement diff --git a/libraries/plugins/src/plugins/DisplayPlugin.h b/libraries/plugins/src/plugins/DisplayPlugin.h index 1f6a16cd46..9cb4e071f3 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.h +++ b/libraries/plugins/src/plugins/DisplayPlugin.h @@ -168,6 +168,26 @@ public: static const QString& MENU_PATH(); + enum Hand { + LeftHand = 0x01, + RightHand = 0x02, + }; + + enum class HandLaserMode { + None, // Render no hand lasers + Overlay, // Render hand lasers only if they intersect with the UI layer, and stop at the UI layer + }; + + virtual bool setHandLaser( + uint32_t hands, // Bits from the Hand enum + HandLaserMode mode, // Mode in which to render + const vec4& color = vec4(1), // The color of the rendered laser + const vec3& direction = vec3(0, 0, -1) // The direction in which to render the hand lasers + ) { + return false; + } + + signals: void recommendedFramebufferSizeChanged(const QSize & size); // Indicates that this display plugin is no longer valid for use. diff --git a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp index e9f8545cff..f5fdbd303c 100644 --- a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp @@ -24,8 +24,9 @@ bool OculusBaseDisplayPlugin::beginFrameRender(uint32_t frameIndex) { auto trackingState = ovr_GetTrackingState(_session, _currentRenderFrameInfo.predictedDisplayTime, ovrTrue); _currentRenderFrameInfo.renderPose = toGlm(trackingState.HeadPose.ThePose); _currentRenderFrameInfo.presentPose = _currentRenderFrameInfo.renderPose; - Lock lock(_mutex); - _frameInfos[frameIndex] = _currentRenderFrameInfo; + withRenderThreadLock([&] { + _frameInfos[frameIndex] = _currentRenderFrameInfo; + }); return true; } diff --git a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp index 8e044fbc16..29a2a4bb1a 100644 --- a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp +++ b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp @@ -45,8 +45,9 @@ bool OculusLegacyDisplayPlugin::beginFrameRender(uint32_t frameIndex) { _currentRenderFrameInfo.predictedDisplayTime = _currentRenderFrameInfo.sensorSampleTime = ovr_GetTimeInSeconds(); _trackingState = ovrHmd_GetTrackingState(_hmd, _currentRenderFrameInfo.predictedDisplayTime); _currentRenderFrameInfo.rawRenderPose = _currentRenderFrameInfo.renderPose = toGlm(_trackingState.HeadPose.ThePose); - Lock lock(_mutex); - _frameInfos[frameIndex] = _currentRenderFrameInfo; + withRenderThreadLock([&]{ + _frameInfos[frameIndex] = _currentRenderFrameInfo; + }) return true; } diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index fbade9fd68..92c01dc0a3 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -29,11 +29,13 @@ Q_DECLARE_LOGGING_CATEGORY(displayplugins) const QString OpenVrDisplayPlugin::NAME("OpenVR (Vive)"); const QString StandingHMDSensorMode = "Standing HMD Sensor Mode"; // this probably shouldn't be hardcoded here -static vr::IVRCompositor* _compositor{ nullptr }; +static vr::IVRCompositor* _compositor { nullptr }; vr::TrackedDevicePose_t _trackedDevicePose[vr::k_unMaxTrackedDeviceCount]; + mat4 _trackedDevicePoseMat4[vr::k_unMaxTrackedDeviceCount]; vec3 _trackedDeviceLinearVelocities[vr::k_unMaxTrackedDeviceCount]; vec3 _trackedDeviceAngularVelocities[vr::k_unMaxTrackedDeviceCount]; + static mat4 _sensorResetMat; static std::array VR_EYES { { vr::Eye_Left, vr::Eye_Right } }; bool _openVrDisplayActive { false }; @@ -59,16 +61,14 @@ bool OpenVrDisplayPlugin::internalActivate() { // left + right eyes _renderTargetSize.x *= 2; - { - Lock lock(_poseMutex); + withRenderThreadLock([&] { openvr_for_each_eye([&](vr::Hmd_Eye eye) { _eyeOffsets[eye] = toGlm(_system->GetEyeToHeadTransform(eye)); _eyeProjections[eye] = toGlm(_system->GetProjectionMatrix(eye, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP, vr::API_OpenGL)); }); // FIXME Calculate the proper combined projection by using GetProjectionRaw values from both eyes _cullingProjection = _eyeProjections[0]; - - } + }); _compositor = vr::VRCompositor(); Q_ASSERT(_compositor); @@ -113,7 +113,7 @@ void OpenVrDisplayPlugin::internalDeactivate() { void OpenVrDisplayPlugin::customizeContext() { // Display plugins in DLLs must initialize glew locally static std::once_flag once; - std::call_once(once, []{ + std::call_once(once, [] { glewExperimental = true; GLenum err = glewInit(); glGetError(); // clear the potential error from glewExperimental @@ -123,9 +123,10 @@ void OpenVrDisplayPlugin::customizeContext() { } void OpenVrDisplayPlugin::resetSensors() { - Lock lock(_poseMutex); - glm::mat4 m = toGlm(_trackedDevicePose[0].mDeviceToAbsoluteTracking); - _sensorResetMat = glm::inverse(cancelOutRollAndPitch(m)); + withRenderThreadLock([&] { + glm::mat4 m = toGlm(_trackedDevicePose[0].mDeviceToAbsoluteTracking); + _sensorResetMat = glm::inverse(cancelOutRollAndPitch(m)); + }); } @@ -150,6 +151,24 @@ bool OpenVrDisplayPlugin::beginFrameRender(uint32_t frameIndex) { _system->GetDeviceToAbsoluteTrackingPose(vr::TrackingUniverseStanding, _currentRenderFrameInfo.predictedDisplayTime, _trackedDevicePose, vr::k_unMaxTrackedDeviceCount); + + vr::TrackedDeviceIndex_t handIndices[2] { vr::k_unTrackedDeviceIndexInvalid, vr::k_unTrackedDeviceIndexInvalid }; + { + vr::TrackedDeviceIndex_t controllerIndices[2] ; + auto trackedCount = _system->GetSortedTrackedDeviceIndicesOfClass(vr::TrackedDeviceClass_Controller, controllerIndices, 2); + // Find the left and right hand controllers, if they exist + for (uint32_t i = 0; i < std::min(trackedCount, 2); ++i) { + if (_trackedDevicePose[i].bPoseIsValid) { + auto role = _system->GetControllerRoleForTrackedDeviceIndex(controllerIndices[i]); + if (vr::TrackedControllerRole_LeftHand == role) { + handIndices[0] = controllerIndices[i]; + } else if (vr::TrackedControllerRole_RightHand == role) { + handIndices[1] = controllerIndices[i]; + } + } + } + } + // copy and process predictedTrackedDevicePoses for (int i = 0; i < vr::k_unMaxTrackedDeviceCount; i++) { _trackedDevicePoseMat4[i] = _sensorResetMat * toGlm(_trackedDevicePose[i].mDeviceToAbsoluteTracking); @@ -159,18 +178,27 @@ bool OpenVrDisplayPlugin::beginFrameRender(uint32_t frameIndex) { _currentRenderFrameInfo.rawRenderPose = toGlm(_trackedDevicePose[vr::k_unTrackedDeviceIndex_Hmd].mDeviceToAbsoluteTracking); _currentRenderFrameInfo.renderPose = _trackedDevicePoseMat4[vr::k_unTrackedDeviceIndex_Hmd]; - Lock lock(_mutex); - _frameInfos[frameIndex] = _currentRenderFrameInfo; + bool keyboardVisible = isOpenVrKeyboardShown(); + withRenderThreadLock([&] { + // Make controller poses available to the presentation thread + for (int i = 0; i < 2; ++i) { + if (keyboardVisible || handIndices[i] == vr::k_unTrackedDeviceIndexInvalid) { + _handPoses[i] = glm::mat4(); + } else { + _handPoses[i] = _sensorResetMat * toGlm(_trackedDevicePose[handIndices[i]].mDeviceToAbsoluteTracking); + } + } + _frameInfos[frameIndex] = _currentRenderFrameInfo; + }); return true; } void OpenVrDisplayPlugin::hmdPresent() { - PROFILE_RANGE_EX(__FUNCTION__, 0xff00ff00, (uint64_t)_currentPresentFrameIndex) // Flip y-axis since GL UV coords are backwards. - static vr::VRTextureBounds_t leftBounds{ 0, 0, 0.5f, 1 }; - static vr::VRTextureBounds_t rightBounds{ 0.5f, 0, 1, 1 }; + static vr::VRTextureBounds_t leftBounds { 0, 0, 0.5f, 1 }; + static vr::VRTextureBounds_t rightBounds { 0.5f, 0, 1, 1 }; vr::Texture_t texture { (void*)oglplus::GetName(_compositeFramebuffer->color), vr::API_OpenGL, vr::ColorSpace_Auto }; @@ -191,6 +219,10 @@ bool OpenVrDisplayPlugin::isHmdMounted() const { } void OpenVrDisplayPlugin::updatePresentPose() { + mat4 sensorResetMat; + withPresentThreadLock([&] { + sensorResetMat = _sensorResetMat; + }); { float fSecondsSinceLastVsync; _system->GetTimeSinceLastVsync(&fSecondsSinceLastVsync, nullptr); @@ -202,9 +234,8 @@ void OpenVrDisplayPlugin::updatePresentPose() { _system->GetDeviceToAbsoluteTrackingPose(vr::TrackingUniverseStanding, fPredictedSecondsFromNow, &pose, 1); _currentPresentFrameInfo.rawPresentPose = toGlm(pose.mDeviceToAbsoluteTracking); } - _currentPresentFrameInfo.presentPose = _sensorResetMat * _currentPresentFrameInfo.rawPresentPose; + _currentPresentFrameInfo.presentPose = sensorResetMat * _currentPresentFrameInfo.rawPresentPose; mat3 renderRotation(_currentPresentFrameInfo.rawRenderPose); mat3 presentRotation(_currentPresentFrameInfo.rawPresentPose); _currentPresentFrameInfo.presentReprojection = glm::mat3(glm::inverse(renderRotation) * presentRotation); } - diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.h b/plugins/openvr/src/OpenVrDisplayPlugin.h index fda5e37c2a..ee693b8091 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.h +++ b/plugins/openvr/src/OpenVrDisplayPlugin.h @@ -43,5 +43,4 @@ private: vr::IVRSystem* _system { nullptr }; std::atomic _hmdActivityLevel { vr::k_EDeviceActivityLevel_Unknown }; static const QString NAME; - mutable Mutex _poseMutex; }; diff --git a/plugins/openvr/src/OpenVrHelpers.cpp b/plugins/openvr/src/OpenVrHelpers.cpp index 1ff1c65ef8..e4cca6ecd6 100644 --- a/plugins/openvr/src/OpenVrHelpers.cpp +++ b/plugins/openvr/src/OpenVrHelpers.cpp @@ -208,6 +208,10 @@ void disableOpenVrKeyboard() { QObject::disconnect(_focusConnection); } +bool isOpenVrKeyboardShown() { + return _keyboardShown; +} + void handleOpenVrEvents() { if (!activeHmd) { diff --git a/plugins/openvr/src/OpenVrHelpers.h b/plugins/openvr/src/OpenVrHelpers.h index 426178cd65..db8bb4f2e8 100644 --- a/plugins/openvr/src/OpenVrHelpers.h +++ b/plugins/openvr/src/OpenVrHelpers.h @@ -20,6 +20,7 @@ void handleOpenVrEvents(); bool openVrQuitRequested(); void enableOpenVrKeyboard(); void disableOpenVrKeyboard(); +bool isOpenVrKeyboardShown(); template From bb3722d91516d120a6dc14592dc93ef330109efd Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 10 Jun 2016 01:36:25 -0700 Subject: [PATCH 36/93] trying again to fix mac build. --- plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp index 29a2a4bb1a..8d2bc24177 100644 --- a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp +++ b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp @@ -47,7 +47,7 @@ bool OculusLegacyDisplayPlugin::beginFrameRender(uint32_t frameIndex) { _currentRenderFrameInfo.rawRenderPose = _currentRenderFrameInfo.renderPose = toGlm(_trackingState.HeadPose.ThePose); withRenderThreadLock([&]{ _frameInfos[frameIndex] = _currentRenderFrameInfo; - }) + }); return true; } From 130c0dda318335d70e1d26de6625fb1bc46112ee Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 10 Jun 2016 10:09:09 -0700 Subject: [PATCH 37/93] PR feedback --- .../display-plugins/src/display-plugins/OpenGLDisplayPlugin.h | 2 +- .../src/display-plugins/hmd/HmdDisplayPlugin.cpp | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h index ec8aa45840..02a30a2570 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -152,7 +152,7 @@ protected: } private: - // Any resource shared by the main thread and the presntaion thread must + // Any resource shared by the main thread and the presentation thread must // be serialized through this mutex mutable Mutex _presentMutex; ProgramPtr _activeProgram; diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index ce1a42971e..9b71d3703b 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -408,9 +408,11 @@ bool HmdDisplayPlugin::setHandLaser(uint32_t hands, HandLaserMode mode, const ve } }); // FIXME defer to a child class plugin to determine if hand lasers are actually + // available based on the presence or absence of hand controllers return true; } +// FIXME try to consolidate the duplication of logic between this function and a similar one in CompsitorHelper. static float calculateRayUiCollisionDistance(const glm::mat4& headPose, const glm::vec3& position, const glm::vec3& direction) { auto relativePosition4 = glm::inverse(headPose) * vec4(position, 1); auto relativePosition = vec3(relativePosition4) / relativePosition4.w; @@ -418,6 +420,7 @@ static float calculateRayUiCollisionDistance(const glm::mat4& headPose, const gl if (glm::abs(glm::length2(relativeDirection) - 1.0f) > EPSILON) { relativeDirection = glm::normalize(relativeDirection); } + // FIXME fetch the actual UI radius from... somewhere? float uiRadius = 1.0f; float instersectionDistance; if (!glm::intersectRaySphere(relativePosition, relativeDirection, vec3(0), uiRadius * uiRadius, instersectionDistance)) { From 62051ad297056a5fd0e07966c9ea0cc108f6f3f4 Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Mon, 13 Jun 2016 14:40:15 -0700 Subject: [PATCH 38/93] Working on touch --- .../controllers/src/controllers/Forward.h | 1 + .../oculus/src/OculusBaseDisplayPlugin.cpp | 10 +++ .../oculus/src/OculusControllerManager.cpp | 82 +---------------- plugins/oculus/src/OculusHelpers.cpp | 88 +++++++++++++++++++ plugins/oculus/src/OculusHelpers.h | 5 ++ 5 files changed, 106 insertions(+), 80 deletions(-) diff --git a/libraries/controllers/src/controllers/Forward.h b/libraries/controllers/src/controllers/Forward.h index e1a62556d4..23dd162831 100644 --- a/libraries/controllers/src/controllers/Forward.h +++ b/libraries/controllers/src/controllers/Forward.h @@ -32,6 +32,7 @@ class Mapping; using MappingPointer = std::shared_ptr; using MappingList = std::list; +struct Pose; } #endif diff --git a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp index f5fdbd303c..a16f630bf8 100644 --- a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp @@ -24,7 +24,17 @@ bool OculusBaseDisplayPlugin::beginFrameRender(uint32_t frameIndex) { auto trackingState = ovr_GetTrackingState(_session, _currentRenderFrameInfo.predictedDisplayTime, ovrTrue); _currentRenderFrameInfo.renderPose = toGlm(trackingState.HeadPose.ThePose); _currentRenderFrameInfo.presentPose = _currentRenderFrameInfo.renderPose; + withRenderThreadLock([&] { + // Make controller poses available to the presentation thread + ovr_for_each_hand([&](ovrHandType hand){ + static const auto REQUIRED_HAND_STATUS = ovrStatus_OrientationTracked & ovrStatus_PositionTracked; + if (REQUIRED_HAND_STATUS == (trackingState.HandStatusFlags[hand] & REQUIRED_HAND_STATUS)) { + _handPoses[hand] = toGlm(trackingState.HandPoses[hand].ThePose); + } else { + _handPoses[hand] = glm::mat4(); + } + }); _frameInfos[frameIndex] = _currentRenderFrameInfo; }); return true; diff --git a/plugins/oculus/src/OculusControllerManager.cpp b/plugins/oculus/src/OculusControllerManager.cpp index 9f0e76363b..0e9ca21804 100644 --- a/plugins/oculus/src/OculusControllerManager.cpp +++ b/plugins/oculus/src/OculusControllerManager.cpp @@ -243,91 +243,13 @@ void OculusControllerManager::TouchDevice::focusOutEvent() { void OculusControllerManager::TouchDevice::handlePose(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, ovrHandType hand, const ovrPoseStatef& handPose) { - // When the sensor-to-world rotation is identity the coordinate axes look like this: - // - // user - // forward - // -z - // | - // y| user - // y o----x right - // o-----x user - // | up - // | - // z - // - // Rift - - // From ABOVE the hand canonical axes looks like this: - // - // | | | | y | | | | - // | | | | | | | | | - // | | | | | - // |left | / x---- + \ |right| - // | _/ z \_ | - // | | | | - // | | | | - // - - // So when the user is in Rift space facing the -zAxis with hands outstretched and palms down - // the rotation to align the Touch axes with those of the hands is: - // - // touchToHand = halfTurnAboutY * quaterTurnAboutX - - // Due to how the Touch controllers fit into the palm there is an offset that is different for each hand. - // You can think of this offset as the inverse of the measured rotation when the hands are posed, such that - // the combination (measurement * offset) is identity at this orientation. - // - // Qoffset = glm::inverse(deltaRotation when hand is posed fingers forward, palm down) - // - // An approximate offset for the Touch can be obtained by inspection: - // - // Qoffset = glm::inverse(glm::angleAxis(sign * PI/2.0f, zAxis) * glm::angleAxis(PI/4.0f, xAxis)) - // - // So the full equation is: - // - // Q = combinedMeasurement * touchToHand - // - // Q = (deltaQ * QOffset) * (yFlip * quarterTurnAboutX) - // - // Q = (deltaQ * inverse(deltaQForAlignedHand)) * (yFlip * quarterTurnAboutX) - auto poseId = hand == ovrHand_Left ? controller::LEFT_HAND : controller::RIGHT_HAND; auto& pose = _poseStateMap[poseId]; - - static const glm::quat yFlip = glm::angleAxis(PI, Vectors::UNIT_Y); - static const glm::quat quarterX = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_X); - static const glm::quat touchToHand = yFlip * quarterX; - - static const glm::quat leftQuarterZ = glm::angleAxis(-PI_OVER_TWO, Vectors::UNIT_Z); - static const glm::quat rightQuarterZ = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_Z); - static const glm::quat eighthX = glm::angleAxis(PI / 4.0f, Vectors::UNIT_X); - - static const glm::quat leftRotationOffset = glm::inverse(leftQuarterZ * eighthX) * touchToHand; - static const glm::quat rightRotationOffset = glm::inverse(rightQuarterZ * eighthX) * touchToHand; - - static const float CONTROLLER_LENGTH_OFFSET = 0.0762f; // three inches - static const glm::vec3 CONTROLLER_OFFSET = glm::vec3(CONTROLLER_LENGTH_OFFSET / 2.0f, - CONTROLLER_LENGTH_OFFSET / 2.0f, - CONTROLLER_LENGTH_OFFSET * 2.0f); - static const glm::vec3 leftTranslationOffset = glm::vec3(-1.0f, 1.0f, 1.0f) * CONTROLLER_OFFSET; - static const glm::vec3 rightTranslationOffset = CONTROLLER_OFFSET; - - auto translationOffset = (hand == ovrHand_Left ? leftTranslationOffset : rightTranslationOffset); - auto rotationOffset = (hand == ovrHand_Left ? leftRotationOffset : rightRotationOffset); - - glm::quat rotation = toGlm(handPose.ThePose.Orientation); - - pose.translation = toGlm(handPose.ThePose.Position); - pose.translation += rotation * translationOffset; - pose.rotation = rotation * rotationOffset; - pose.angularVelocity = toGlm(handPose.AngularVelocity); - pose.velocity = toGlm(handPose.LinearVelocity); - pose.valid = true; - + pose = ovrControllerPoseToHandPose(hand, handPose); // transform into avatar frame glm::mat4 controllerToAvatar = glm::inverse(inputCalibrationData.avatarMat) * inputCalibrationData.sensorToWorldMat; pose = pose.transform(controllerToAvatar); + } bool OculusControllerManager::TouchDevice::triggerHapticPulse(float strength, float duration, controller::Hand hand) { diff --git a/plugins/oculus/src/OculusHelpers.cpp b/plugins/oculus/src/OculusHelpers.cpp index 6ddace684b..705c0a0781 100644 --- a/plugins/oculus/src/OculusHelpers.cpp +++ b/plugins/oculus/src/OculusHelpers.cpp @@ -15,6 +15,9 @@ #include #include +#include +#include + using Mutex = std::mutex; using Lock = std::unique_lock; @@ -191,3 +194,88 @@ void SwapFramebufferWrapper::onBind(oglplus::Framebuffer::Target target) { void SwapFramebufferWrapper::onUnbind(oglplus::Framebuffer::Target target) { glFramebufferTexture2D(toEnum(target), GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); } + + +controller::Pose ovrControllerPoseToHandPose( + ovrHandType hand, + const ovrPoseStatef& handPose) { + // When the sensor-to-world rotation is identity the coordinate axes look like this: + // + // user + // forward + // -z + // | + // y| user + // y o----x right + // o-----x user + // | up + // | + // z + // + // Rift + + // From ABOVE the hand canonical axes looks like this: + // + // | | | | y | | | | + // | | | | | | | | | + // | | | | | + // |left | / x---- + \ |right| + // | _/ z \_ | + // | | | | + // | | | | + // + + // So when the user is in Rift space facing the -zAxis with hands outstretched and palms down + // the rotation to align the Touch axes with those of the hands is: + // + // touchToHand = halfTurnAboutY * quaterTurnAboutX + + // Due to how the Touch controllers fit into the palm there is an offset that is different for each hand. + // You can think of this offset as the inverse of the measured rotation when the hands are posed, such that + // the combination (measurement * offset) is identity at this orientation. + // + // Qoffset = glm::inverse(deltaRotation when hand is posed fingers forward, palm down) + // + // An approximate offset for the Touch can be obtained by inspection: + // + // Qoffset = glm::inverse(glm::angleAxis(sign * PI/2.0f, zAxis) * glm::angleAxis(PI/4.0f, xAxis)) + // + // So the full equation is: + // + // Q = combinedMeasurement * touchToHand + // + // Q = (deltaQ * QOffset) * (yFlip * quarterTurnAboutX) + // + // Q = (deltaQ * inverse(deltaQForAlignedHand)) * (yFlip * quarterTurnAboutX) + static const glm::quat yFlip = glm::angleAxis(PI, Vectors::UNIT_Y); + static const glm::quat quarterX = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_X); + static const glm::quat touchToHand = yFlip * quarterX; + + static const glm::quat leftQuarterZ = glm::angleAxis(-PI_OVER_TWO, Vectors::UNIT_Z); + static const glm::quat rightQuarterZ = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_Z); + static const glm::quat eighthX = glm::angleAxis(PI / 4.0f, Vectors::UNIT_X); + + static const glm::quat leftRotationOffset = glm::inverse(leftQuarterZ * eighthX) * touchToHand; + static const glm::quat rightRotationOffset = glm::inverse(rightQuarterZ * eighthX) * touchToHand; + + static const float CONTROLLER_LENGTH_OFFSET = 0.0762f; // three inches + static const glm::vec3 CONTROLLER_OFFSET = glm::vec3(CONTROLLER_LENGTH_OFFSET / 2.0f, + CONTROLLER_LENGTH_OFFSET / 2.0f, + CONTROLLER_LENGTH_OFFSET * 2.0f); + static const glm::vec3 leftTranslationOffset = glm::vec3(-1.0f, 1.0f, 1.0f) * CONTROLLER_OFFSET; + static const glm::vec3 rightTranslationOffset = CONTROLLER_OFFSET; + + auto translationOffset = (hand == ovrHand_Left ? leftTranslationOffset : rightTranslationOffset); + auto rotationOffset = (hand == ovrHand_Left ? leftRotationOffset : rightRotationOffset); + + glm::quat rotation = toGlm(handPose.ThePose.Orientation); + + controller::Pose pose; + pose.translation = toGlm(handPose.ThePose.Position); + pose.translation += rotation * translationOffset; + pose.rotation = rotation * rotationOffset; + pose.angularVelocity = toGlm(handPose.AngularVelocity); + pose.velocity = toGlm(handPose.LinearVelocity); + pose.valid = true; + return pose; +} \ No newline at end of file diff --git a/plugins/oculus/src/OculusHelpers.h b/plugins/oculus/src/OculusHelpers.h index 2f13c45466..66cdccf15a 100644 --- a/plugins/oculus/src/OculusHelpers.h +++ b/plugins/oculus/src/OculusHelpers.h @@ -13,6 +13,7 @@ #include #include +#include void logWarning(const char* what); void logFatal(const char* what); @@ -128,3 +129,7 @@ protected: private: ovrSession _session; }; + +controller::Pose ovrControllerPoseToHandPose( + ovrHandType hand, + const ovrPoseStatef& handPose); From 6d2181f0f137d8789efabb341bb5b64053edbcd1 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 13 Jun 2016 11:59:38 -0700 Subject: [PATCH 39/93] Combine DISTANCE_HOLDING and CONTINUE_DISTANCE_HOLDING states. --- .../system/controllers/handControllerGrab.js | 41 +++++++++---------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 25cd100991..f4bcefe3ac 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -157,20 +157,19 @@ var STATE_OFF = 0; var STATE_SEARCHING = 1; var STATE_HOLD_SEARCHING = 2; var STATE_DISTANCE_HOLDING = 3; -var STATE_CONTINUE_DISTANCE_HOLDING = 4; -var STATE_NEAR_GRABBING = 5; -var STATE_CONTINUE_NEAR_GRABBING = 6; -var STATE_NEAR_TRIGGER = 7; -var STATE_CONTINUE_NEAR_TRIGGER = 8; -var STATE_FAR_TRIGGER = 9; -var STATE_CONTINUE_FAR_TRIGGER = 10; -var STATE_RELEASE = 11; -var STATE_EQUIP = 12; -var STATE_HOLD = 13; -var STATE_CONTINUE_HOLD = 14; -var STATE_CONTINUE_EQUIP = 15; -var STATE_WAITING_FOR_RELEASE_THUMB_RELEASE = 16; -var STATE_WAITING_FOR_EQUIP_THUMB_RELEASE = 17; +var STATE_NEAR_GRABBING = 4; +var STATE_CONTINUE_NEAR_GRABBING = 5; +var STATE_NEAR_TRIGGER = 6; +var STATE_CONTINUE_NEAR_TRIGGER = 7; +var STATE_FAR_TRIGGER = 8; +var STATE_CONTINUE_FAR_TRIGGER = 9; +var STATE_RELEASE = 10; +var STATE_EQUIP = 11; +var STATE_HOLD = 12; +var STATE_CONTINUE_HOLD = 13; +var STATE_CONTINUE_EQUIP = 14; +var STATE_WAITING_FOR_RELEASE_THUMB_RELEASE = 15; +var STATE_WAITING_FOR_EQUIP_THUMB_RELEASE = 16; // "collidesWith" is specified by comma-separated list of group names // the possible group names are: static, dynamic, kinematic, myAvatar, otherAvatar @@ -198,12 +197,9 @@ CONTROLLER_STATE_MACHINE[STATE_HOLD_SEARCHING] = { }; CONTROLLER_STATE_MACHINE[STATE_DISTANCE_HOLDING] = { name: "distance_holding", + enterMethod: "distanceHoldingEnter", updateMethod: "distanceHolding" }; -CONTROLLER_STATE_MACHINE[STATE_CONTINUE_DISTANCE_HOLDING] = { - name: "continue_distance_holding", - updateMethod: "continueDistanceHolding" -}; CONTROLLER_STATE_MACHINE[STATE_NEAR_GRABBING] = { name: "near_grabbing", updateMethod: "nearGrabbing" @@ -1207,7 +1203,7 @@ function MyController(hand) { return (dimensions.x * dimensions.y * dimensions.z) * density; } - this.distanceHolding = function() { + this.distanceHoldingEnter = function() { // controller pose is in avatar frame var avatarControllerPose = @@ -1256,9 +1252,12 @@ function MyController(hand) { this.actionTimeout = now + (ACTION_TTL * MSECS_PER_SEC); if (this.actionID !== null) { - this.setState(STATE_CONTINUE_DISTANCE_HOLDING); this.activateEntity(this.grabbedEntity, grabbedProperties, false); this.callEntityMethodOnGrabbed("startDistanceGrab"); + } else { + // addAction failed? + this.setState(STATE_RELEASE); + return; } this.turnOffVisualizations(); @@ -1267,7 +1266,7 @@ function MyController(hand) { this.previousControllerRotation = controllerRotation; }; - this.continueDistanceHolding = function() { + this.distanceHolding = function() { if (this.triggerSmoothedReleased() && this.secondaryReleased()) { this.setState(STATE_RELEASE); this.callEntityMethodOnGrabbed("releaseGrab"); From bd3326d2fc45c66da5e403de99d4d1f905c8539c Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 13 Jun 2016 13:59:10 -0700 Subject: [PATCH 40/93] Collapse continueNearGrabbing and nearGrabbing into a single state. * Moved the update logic from STATE_NEAR_GRABBING, STATE_HOLD & STATE_EQUIP into the entryMethods for those states. * Removed STATE_CONTINUE_NEAR_GRABBING, STATE_CONTINUE_HOLD & STATE_CONTINUE_EQUIP states This functionality has been moved into the updateMethod for their respective states. This *should* be a pure re-factor no functionality was changed. --- .../system/controllers/handControllerGrab.js | 76 +++++-------------- 1 file changed, 19 insertions(+), 57 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index f4bcefe3ac..3e611b9f00 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -19,7 +19,7 @@ Script.include("/~/system/libraries/utils.js"); // add lines where the hand ray picking is happening // var WANT_DEBUG = false; -var WANT_DEBUG_STATE = false; +var WANT_DEBUG_STATE = true; var WANT_DEBUG_SEARCH_NAME = null; // @@ -158,7 +158,6 @@ var STATE_SEARCHING = 1; var STATE_HOLD_SEARCHING = 2; var STATE_DISTANCE_HOLDING = 3; var STATE_NEAR_GRABBING = 4; -var STATE_CONTINUE_NEAR_GRABBING = 5; var STATE_NEAR_TRIGGER = 6; var STATE_CONTINUE_NEAR_TRIGGER = 7; var STATE_FAR_TRIGGER = 8; @@ -166,8 +165,6 @@ var STATE_CONTINUE_FAR_TRIGGER = 9; var STATE_RELEASE = 10; var STATE_EQUIP = 11; var STATE_HOLD = 12; -var STATE_CONTINUE_HOLD = 13; -var STATE_CONTINUE_EQUIP = 14; var STATE_WAITING_FOR_RELEASE_THUMB_RELEASE = 15; var STATE_WAITING_FOR_EQUIP_THUMB_RELEASE = 16; @@ -202,27 +199,18 @@ CONTROLLER_STATE_MACHINE[STATE_DISTANCE_HOLDING] = { }; CONTROLLER_STATE_MACHINE[STATE_NEAR_GRABBING] = { name: "near_grabbing", + enterMethod: "nearGrabbingEnter", updateMethod: "nearGrabbing" }; CONTROLLER_STATE_MACHINE[STATE_EQUIP] = { name: "equip", + enterMethod: "nearGrabbingEnter", updateMethod: "nearGrabbing" }; CONTROLLER_STATE_MACHINE[STATE_HOLD] = { - name: "hold", - updateMethod: "nearGrabbing" -}; -CONTROLLER_STATE_MACHINE[STATE_CONTINUE_NEAR_GRABBING] = { - name: "continue_near_grabbing", - updateMethod: "continueNearGrabbing" -}; -CONTROLLER_STATE_MACHINE[STATE_CONTINUE_HOLD] = { name: "continue_hold", - updateMethod: "continueNearGrabbing" -}; -CONTROLLER_STATE_MACHINE[STATE_CONTINUE_EQUIP] = { - name: "continue_equip", - updateMethod: "continueNearGrabbing" + enterMethod: "nearGrabbingEnter", + updateMethod: "nearGrabbing" }; CONTROLLER_STATE_MACHINE[STATE_NEAR_TRIGGER] = { name: "near_trigger", @@ -400,6 +388,8 @@ function MyController(hand) { print("WARNING: could not find state " + this.state + " in state machine"); } + this.state = newState; + // enter the new state if (CONTROLLER_STATE_MACHINE[newState]) { var enterMethodName = CONTROLLER_STATE_MACHINE[newState].enterMethod; @@ -410,8 +400,6 @@ function MyController(hand) { } else { print("WARNING: could not find newState " + newState + " in state machine"); } - - this.state = newState; }; this.debugLine = function(closePoint, farPoint, color) { @@ -1254,10 +1242,6 @@ function MyController(hand) { if (this.actionID !== null) { this.activateEntity(this.grabbedEntity, grabbedProperties, false); this.callEntityMethodOnGrabbed("startDistanceGrab"); - } else { - // addAction failed? - this.setState(STATE_RELEASE); - return; } this.turnOffVisualizations(); @@ -1483,20 +1467,9 @@ function MyController(hand) { } } - this.nearGrabbing = function() { + this.nearGrabbingEnter = function() { var now = Date.now(); - if (this.state == STATE_NEAR_GRABBING && this.triggerSmoothedReleased()) { - this.setState(STATE_RELEASE); - this.callEntityMethodOnGrabbed("releaseGrab"); - return; - } - if (this.state == STATE_HOLD && this.secondaryReleased()) { - this.setState(STATE_RELEASE); - this.callEntityMethodOnGrabbed("releaseGrab"); - return; - } - this.lineOff(); this.overlayLineOff(); @@ -1578,17 +1551,6 @@ function MyController(hand) { this.callEntityMethodOnGrabbed("startEquip"); } - if (this.state == STATE_NEAR_GRABBING) { - // near grabbing - this.setState(STATE_CONTINUE_NEAR_GRABBING); - } else if (this.state == STATE_HOLD) { - // holding - this.setState(STATE_CONTINUE_HOLD); - } else { // (this.state == STATE_EQUIP) - // equipping - this.setState(STATE_CONTINUE_EQUIP); - } - this.currentHandControllerTipPosition = (this.hand === RIGHT_HAND) ? MyAvatar.rightHandTipPosition : MyAvatar.leftHandTipPosition; this.currentObjectTime = Date.now(); @@ -1599,29 +1561,29 @@ function MyController(hand) { this.currentAngularVelocity = ZERO_VEC; }; - this.continueNearGrabbing = function() { - if (this.state == STATE_CONTINUE_NEAR_GRABBING && this.triggerSmoothedReleased()) { + this.nearGrabbing = function() { + if (this.state == STATE_NEAR_GRABBING && this.triggerSmoothedReleased()) { this.setState(STATE_RELEASE); this.callEntityMethodOnGrabbed("releaseGrab"); return; } - if (this.state == STATE_CONTINUE_HOLD && this.secondaryReleased()) { + if (this.state == STATE_HOLD && this.secondaryReleased()) { this.setState(STATE_RELEASE); this.callEntityMethodOnGrabbed("releaseEquip"); return; } - if (this.state == STATE_CONTINUE_EQUIP && this.thumbPressed()) { + if (this.state == STATE_EQUIP && this.thumbPressed()) { this.setState(STATE_WAITING_FOR_RELEASE_THUMB_RELEASE); this.callEntityMethodOnGrabbed("releaseEquip"); return; } - if (this.state == STATE_CONTINUE_NEAR_GRABBING && this.thumbPressed()) { + if (this.state == STATE_NEAR_GRABBING && this.thumbPressed()) { this.setState(STATE_WAITING_FOR_EQUIP_THUMB_RELEASE); this.callEntityMethodOnGrabbed("releaseGrab"); this.callEntityMethodOnGrabbed("startEquip"); return; } - if (this.state == STATE_CONTINUE_HOLD && this.thumbPressed()) { + if (this.state == STATE_HOLD && this.thumbPressed()) { this.setState(STATE_WAITING_FOR_EQUIP_THUMB_RELEASE); return; } @@ -1652,9 +1614,9 @@ function MyController(hand) { print("handControllerGrab -- autoreleasing held or equipped item because it is far from hand." + props.parentID + " " + vec3toStr(props.position)); this.setState(STATE_RELEASE); - if (this.state == STATE_CONTINUE_NEAR_GRABBING) { + if (this.state == STATE_NEAR_GRABBING) { this.callEntityMethodOnGrabbed("releaseGrab"); - } else { // (this.state == STATE_CONTINUE_EQUIP || this.state == STATE_CONTINUE_HOLD) + } else { // (this.state == STATE_EQUIP || this.state == STATE_HOLD) this.callEntityMethodOnGrabbed("releaseEquip"); } return; @@ -1693,10 +1655,10 @@ function MyController(hand) { this.currentObjectTime = now; var grabData = getEntityCustomData(GRAB_USER_DATA_KEY, this.grabbedEntity, {}); - if (this.state === STATE_CONTINUE_EQUIP || this.state === STATE_CONTINUE_HOLD) { + if (this.state === STATE_EQUIP || this.state === STATE_HOLD) { this.callEntityMethodOnGrabbed("continueEquip"); } - if (this.state == STATE_CONTINUE_NEAR_GRABBING) { + if (this.state == STATE_NEAR_GRABBING) { this.callEntityMethodOnGrabbed("continueNearGrab"); } @@ -2029,7 +1991,7 @@ function MyController(hand) { this.activateEntity(this.grabbedEntity, loadedProps, true); this.isInitialGrab = true; this.callEntityMethodOnGrabbed("startEquip"); - this.setState(STATE_CONTINUE_EQUIP); + this.setState(STATE_EQUIP); } } }; From fe65df350bc23282cd5d3260a9de69dafba6efb1 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 13 Jun 2016 15:05:34 -0700 Subject: [PATCH 41/93] Combined the release and off states. --- .../system/controllers/handControllerGrab.js | 35 ++++++++----------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 3e611b9f00..386a4905f1 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -162,7 +162,6 @@ var STATE_NEAR_TRIGGER = 6; var STATE_CONTINUE_NEAR_TRIGGER = 7; var STATE_FAR_TRIGGER = 8; var STATE_CONTINUE_FAR_TRIGGER = 9; -var STATE_RELEASE = 10; var STATE_EQUIP = 11; var STATE_HOLD = 12; var STATE_WAITING_FOR_RELEASE_THUMB_RELEASE = 15; @@ -180,6 +179,7 @@ var CONTROLLER_STATE_MACHINE = {}; CONTROLLER_STATE_MACHINE[STATE_OFF] = { name: "off", + enterMethod: "offEnter", updateMethod: "off" }; CONTROLLER_STATE_MACHINE[STATE_SEARCHING] = { @@ -228,10 +228,6 @@ CONTROLLER_STATE_MACHINE[STATE_CONTINUE_FAR_TRIGGER] = { name: "continue_far_trigger", updateMethod: "continueFarTrigger" }; -CONTROLLER_STATE_MACHINE[STATE_RELEASE] = { - name: "release", - updateMethod: "release" -}; CONTROLLER_STATE_MACHINE[STATE_WAITING_FOR_EQUIP_THUMB_RELEASE] = { name: "waiting_for_equip_thumb_release", updateMethod: "waitingForEquipThumbRelease" @@ -918,12 +914,12 @@ function MyController(hand) { this.checkForStrayChildren(); if (this.state == STATE_SEARCHING && this.triggerSmoothedReleased()) { - this.setState(STATE_RELEASE); + this.setState(STATE_OFF); return; } if (this.state == STATE_HOLD_SEARCHING && this.secondaryReleased()) { - this.setState(STATE_RELEASE); + this.setState(STATE_OFF); return; } @@ -1252,7 +1248,7 @@ function MyController(hand) { this.distanceHolding = function() { if (this.triggerSmoothedReleased() && this.secondaryReleased()) { - this.setState(STATE_RELEASE); + this.setState(STATE_OFF); this.callEntityMethodOnGrabbed("releaseGrab"); return; } @@ -1563,12 +1559,12 @@ function MyController(hand) { this.nearGrabbing = function() { if (this.state == STATE_NEAR_GRABBING && this.triggerSmoothedReleased()) { - this.setState(STATE_RELEASE); + this.setState(STATE_OFF); this.callEntityMethodOnGrabbed("releaseGrab"); return; } if (this.state == STATE_HOLD && this.secondaryReleased()) { - this.setState(STATE_RELEASE); + this.setState(STATE_OFF); this.callEntityMethodOnGrabbed("releaseEquip"); return; } @@ -1593,7 +1589,7 @@ function MyController(hand) { var props = Entities.getEntityProperties(this.grabbedEntity, ["localPosition", "parentID", "position", "rotation"]); if (!props.position) { // server may have reset, taking our equipped entity with it. move back to "off" stte - this.setState(STATE_RELEASE); + this.setState(STATE_OFF); this.callEntityMethodOnGrabbed("releaseGrab"); return; } @@ -1613,7 +1609,7 @@ function MyController(hand) { // for whatever reason, the held/equipped entity has been pulled away. ungrab or unequip. print("handControllerGrab -- autoreleasing held or equipped item because it is far from hand." + props.parentID + " " + vec3toStr(props.position)); - this.setState(STATE_RELEASE); + this.setState(STATE_OFF); if (this.state == STATE_NEAR_GRABBING) { this.callEntityMethodOnGrabbed("releaseGrab"); } else { // (this.state == STATE_EQUIP || this.state == STATE_HOLD) @@ -1691,13 +1687,13 @@ function MyController(hand) { }; this.waitingForReleaseThumbRelease = function() { if (this.thumbReleased() && this.triggerSmoothedReleased()) { - this.setState(STATE_RELEASE); + this.setState(STATE_OFF); } }; this.nearTrigger = function() { if (this.triggerSmoothedReleased() && this.secondaryReleased()) { - this.setState(STATE_RELEASE); + this.setState(STATE_OFF); this.callEntityMethodOnGrabbed("stopNearTrigger"); return; } @@ -1707,7 +1703,7 @@ function MyController(hand) { this.farTrigger = function() { if (this.triggerSmoothedReleased() && this.secondaryReleased()) { - this.setState(STATE_RELEASE); + this.setState(STATE_OFF); this.callEntityMethodOnGrabbed("stopFarTrigger"); return; } @@ -1717,7 +1713,7 @@ function MyController(hand) { this.continueNearTrigger = function() { if (this.triggerSmoothedReleased() && this.secondaryReleased()) { - this.setState(STATE_RELEASE); + this.setState(STATE_OFF); this.callEntityMethodOnGrabbed("stopNearTrigger"); return; } @@ -1726,7 +1722,7 @@ function MyController(hand) { this.continueFarTrigger = function() { if (this.triggerSmoothedReleased() && this.secondaryReleased()) { - this.setState(STATE_RELEASE); + this.setState(STATE_OFF); this.callEntityMethodOnGrabbed("stopFarTrigger"); return; } @@ -1743,7 +1739,7 @@ function MyController(hand) { if (intersection.accurate) { this.lastPickTime = now; if (intersection.entityID != this.grabbedEntity) { - this.setState(STATE_RELEASE); + this.setState(STATE_OFF); this.callEntityMethodOnGrabbed("stopFarTrigger"); return; } @@ -1757,7 +1753,7 @@ function MyController(hand) { this.callEntityMethodOnGrabbed("continueFarTrigger"); }; - this.release = function() { + this.offEnter = function() { this.turnLightsOff(); this.turnOffVisualizations(); @@ -1780,7 +1776,6 @@ function MyController(hand) { this.deactivateEntity(this.grabbedEntity, noVelocity); this.actionID = null; - this.setState(STATE_OFF); Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ action: 'release', From b9bf7977535e4780bf99cb14f97d4413927bf8b5 Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Mon, 13 Jun 2016 15:26:48 -0700 Subject: [PATCH 42/93] Fixing issues with unclosed groups in settings persistence --- libraries/networking/src/AccountManager.cpp | 1 + libraries/script-engine/src/ScriptEngines.cpp | 1 + libraries/shared/src/SettingHandle.cpp | 9 +++++++++ libraries/shared/src/SettingHandle.h | 2 ++ libraries/shared/src/SettingInterface.cpp | 2 ++ 5 files changed, 15 insertions(+) diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index bac031885f..26b3801ec1 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -173,6 +173,7 @@ void AccountManager::setAuthURL(const QUrl& authURL) { << "from previous settings file"; } } + settings.endGroup(); if (_accountInfo.getAccessToken().token.isEmpty()) { qCWarning(networking) << "Unable to load account file. No existing account settings will be loaded."; diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp index 29c223f4b3..beddc21787 100644 --- a/libraries/script-engine/src/ScriptEngines.cpp +++ b/libraries/script-engine/src/ScriptEngines.cpp @@ -334,6 +334,7 @@ void ScriptEngines::clearScripts() { Settings settings; settings.beginWriteArray(SETTINGS_KEY); settings.remove(""); + settings.endArray(); } void ScriptEngines::saveScripts() { diff --git a/libraries/shared/src/SettingHandle.cpp b/libraries/shared/src/SettingHandle.cpp index b2f23f5a04..13f9ea48ce 100644 --- a/libraries/shared/src/SettingHandle.cpp +++ b/libraries/shared/src/SettingHandle.cpp @@ -18,6 +18,7 @@ const QString Settings::firstRun { "firstRun" }; + Settings::Settings() : _manager(DependencyManager::get()), _locker(&(_manager->getLock())) @@ -25,6 +26,9 @@ Settings::Settings() : } Settings::~Settings() { + if (_prefixes.size() != 0) { + qFatal("Unstable Settings Prefixes: You must call endGroup for every beginGroup and endArray for every begin*Array call"); + } } void Settings::remove(const QString& key) { @@ -50,14 +54,17 @@ bool Settings::contains(const QString& key) const { } int Settings::beginReadArray(const QString & prefix) { + _prefixes.push(prefix); return _manager->beginReadArray(prefix); } void Settings::beginWriteArray(const QString& prefix, int size) { + _prefixes.push(prefix); _manager->beginWriteArray(prefix, size); } void Settings::endArray() { + _prefixes.pop(); _manager->endArray(); } @@ -66,10 +73,12 @@ void Settings::setArrayIndex(int i) { } void Settings::beginGroup(const QString& prefix) { + _prefixes.push(prefix); _manager->beginGroup(prefix); } void Settings::endGroup() { + _prefixes.pop(); _manager->endGroup(); } diff --git a/libraries/shared/src/SettingHandle.h b/libraries/shared/src/SettingHandle.h index e83c563036..f19fc5875b 100644 --- a/libraries/shared/src/SettingHandle.h +++ b/libraries/shared/src/SettingHandle.h @@ -58,8 +58,10 @@ public: void setQuatValue(const QString& name, const glm::quat& quatValue); void getQuatValueIfValid(const QString& name, glm::quat& quatValue); +private: QSharedPointer _manager; QWriteLocker _locker; + QStack _prefixes; }; namespace Setting { diff --git a/libraries/shared/src/SettingInterface.cpp b/libraries/shared/src/SettingInterface.cpp index 630d52bf7d..1ebaa5cf82 100644 --- a/libraries/shared/src/SettingInterface.cpp +++ b/libraries/shared/src/SettingInterface.cpp @@ -98,6 +98,8 @@ namespace Setting { // Register Handle manager->registerHandle(this); _isInitialized = true; + } else { + qWarning() << "Settings interface used after manager destroyed"; } // Load value from disk From b01eb0439df25f813f1f7f9d329bd1cda0266cc5 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 13 Jun 2016 16:49:38 -0700 Subject: [PATCH 43/93] removed sticky thumb equip states. * removed STATE_WAITING_FOR_EQUIP_THUMB_RELEASE and waitingForEquipThumbRelease * removed STATE_EQUIP * removed STATE_WAITING_FOR_RELEASE_THUMB_RELEASE and waitingForReleaseThumbRelease * removed 'Hifi-Object-Manipulation' 'loaded' support and checkNewlyLoaded method. --- .../system/controllers/handControllerGrab.js | 84 ++++--------------- 1 file changed, 14 insertions(+), 70 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 386a4905f1..2857c91936 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -158,14 +158,11 @@ var STATE_SEARCHING = 1; var STATE_HOLD_SEARCHING = 2; var STATE_DISTANCE_HOLDING = 3; var STATE_NEAR_GRABBING = 4; -var STATE_NEAR_TRIGGER = 6; -var STATE_CONTINUE_NEAR_TRIGGER = 7; -var STATE_FAR_TRIGGER = 8; -var STATE_CONTINUE_FAR_TRIGGER = 9; -var STATE_EQUIP = 11; -var STATE_HOLD = 12; -var STATE_WAITING_FOR_RELEASE_THUMB_RELEASE = 15; -var STATE_WAITING_FOR_EQUIP_THUMB_RELEASE = 16; +var STATE_NEAR_TRIGGER = 4; +var STATE_CONTINUE_NEAR_TRIGGER = 6; +var STATE_FAR_TRIGGER = 7; +var STATE_CONTINUE_FAR_TRIGGER = 8; +var STATE_HOLD = 9; // "collidesWith" is specified by comma-separated list of group names // the possible group names are: static, dynamic, kinematic, myAvatar, otherAvatar @@ -202,11 +199,6 @@ CONTROLLER_STATE_MACHINE[STATE_NEAR_GRABBING] = { enterMethod: "nearGrabbingEnter", updateMethod: "nearGrabbing" }; -CONTROLLER_STATE_MACHINE[STATE_EQUIP] = { - name: "equip", - enterMethod: "nearGrabbingEnter", - updateMethod: "nearGrabbing" -}; CONTROLLER_STATE_MACHINE[STATE_HOLD] = { name: "continue_hold", enterMethod: "nearGrabbingEnter", @@ -228,14 +220,6 @@ CONTROLLER_STATE_MACHINE[STATE_CONTINUE_FAR_TRIGGER] = { name: "continue_far_trigger", updateMethod: "continueFarTrigger" }; -CONTROLLER_STATE_MACHINE[STATE_WAITING_FOR_EQUIP_THUMB_RELEASE] = { - name: "waiting_for_equip_thumb_release", - updateMethod: "waitingForEquipThumbRelease" -}; -CONTROLLER_STATE_MACHINE[STATE_WAITING_FOR_RELEASE_THUMB_RELEASE] = { - name: "waiting_for_release_thumb_release", - updateMethod: "waitingForReleaseThumbRelease" -}; function stateToName(state) { return CONTROLLER_STATE_MACHINE[state] ? CONTROLLER_STATE_MACHINE[state].name : "???"; @@ -1483,7 +1467,7 @@ function MyController(hand) { var handPosition = this.getHandPosition(); var hasPresetPosition = false; - if ((this.state == STATE_EQUIP || this.state == STATE_HOLD) && this.hasPresetOffsets()) { + if (this.state == STATE_HOLD && this.hasPresetOffsets()) { var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); // if an object is "equipped" and has a predefined offset, use it. this.ignoreIK = grabbableData.ignoreIK ? grabbableData.ignoreIK : false; @@ -1499,7 +1483,7 @@ function MyController(hand) { var currentObjectPosition = grabbedProperties.position; var offset = Vec3.subtract(currentObjectPosition, handPosition); this.offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, this.offsetRotation)), offset); - if (this.temporaryPositionOffset && (this.state == STATE_EQUIP)) { + if (this.temporaryPositionOffset) { this.offsetPosition = this.temporaryPositionOffset; // hasPresetPosition = true; } @@ -1543,7 +1527,7 @@ function MyController(hand) { if (this.state == STATE_NEAR_GRABBING) { this.callEntityMethodOnGrabbed("startNearGrab"); - } else { // this.state == STATE_EQUIP || this.state == STATE_HOLD + } else { // this.state == STATE_HOLD this.callEntityMethodOnGrabbed("startEquip"); } @@ -1568,21 +1552,6 @@ function MyController(hand) { this.callEntityMethodOnGrabbed("releaseEquip"); return; } - if (this.state == STATE_EQUIP && this.thumbPressed()) { - this.setState(STATE_WAITING_FOR_RELEASE_THUMB_RELEASE); - this.callEntityMethodOnGrabbed("releaseEquip"); - return; - } - if (this.state == STATE_NEAR_GRABBING && this.thumbPressed()) { - this.setState(STATE_WAITING_FOR_EQUIP_THUMB_RELEASE); - this.callEntityMethodOnGrabbed("releaseGrab"); - this.callEntityMethodOnGrabbed("startEquip"); - return; - } - if (this.state == STATE_HOLD && this.thumbPressed()) { - this.setState(STATE_WAITING_FOR_EQUIP_THUMB_RELEASE); - return; - } this.heartBeat(this.grabbedEntity); @@ -1612,7 +1581,7 @@ function MyController(hand) { this.setState(STATE_OFF); if (this.state == STATE_NEAR_GRABBING) { this.callEntityMethodOnGrabbed("releaseGrab"); - } else { // (this.state == STATE_EQUIP || this.state == STATE_HOLD) + } else { // this.state == STATE_HOLD this.callEntityMethodOnGrabbed("releaseEquip"); } return; @@ -1651,7 +1620,7 @@ function MyController(hand) { this.currentObjectTime = now; var grabData = getEntityCustomData(GRAB_USER_DATA_KEY, this.grabbedEntity, {}); - if (this.state === STATE_EQUIP || this.state === STATE_HOLD) { + if (this.state === STATE_HOLD) { this.callEntityMethodOnGrabbed("continueEquip"); } if (this.state == STATE_NEAR_GRABBING) { @@ -1680,17 +1649,6 @@ function MyController(hand) { } }; - this.waitingForEquipThumbRelease = function() { - if (this.thumbReleased() && this.triggerSmoothedReleased()) { - this.setState(STATE_EQUIP); - } - }; - this.waitingForReleaseThumbRelease = function() { - if (this.thumbReleased() && this.triggerSmoothedReleased()) { - this.setState(STATE_OFF); - } - }; - this.nearTrigger = function() { if (this.triggerSmoothedReleased() && this.secondaryReleased()) { this.setState(STATE_OFF); @@ -1968,6 +1926,8 @@ function MyController(hand) { setEntityCustomData(GRAB_USER_DATA_KEY, entityID, data); }; + // AJT: WTF TO DO WITH THIS? + /* this.checkNewlyLoaded = function(loadedEntityID) { if (this.state == STATE_OFF || this.state == STATE_SEARCHING || @@ -1989,6 +1949,7 @@ function MyController(hand) { this.setState(STATE_EQUIP); } } + */ }; var rightController = new MyController(RIGHT_HAND); @@ -2044,7 +2005,7 @@ handleHandMessages = function(channel, message, sender) { var data = JSON.parse(message); var selectedController = (data.hand === 'left') ? leftController : rightController; selectedController.release(); - selectedController.setState(STATE_EQUIP); + selectedController.setState(STATE_HOLD); selectedController.grabbedEntity = data.entityID; } catch (e) {} @@ -2066,23 +2027,6 @@ handleHandMessages = function(channel, message, sender) { } } catch (e) {} - } else if (channel === 'Hifi-Object-Manipulation') { - if (sender !== MyAvatar.sessionUUID) { - return; - } - - var parsedMessage = null; - try { - parsedMessage = JSON.parse(message); - } catch (e) { - print('error parsing Hifi-Object-Manipulation message'); - return; - } - - if (parsedMessage.action === 'loaded') { - rightController.checkNewlyLoaded(parsedMessage['grabbedEntity']); - leftController.checkNewlyLoaded(parsedMessage['grabbedEntity']); - } } } } From 526fc7d062366dbf3b2be7c8e87b80c70d01e391 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Mon, 13 Jun 2016 17:13:10 -0700 Subject: [PATCH 44/93] Make handControllerGrab independent of whether we're using laser or Reticle. --- scripts/system/controllers/handControllerGrab.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 25cd100991..53c16f26c1 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -300,7 +300,10 @@ function propsArePhysical(props) { // and we should not be showing lasers when someone else is using the Reticle to indicate a 2D minor mode. var EXTERNALLY_MANAGED_2D_MINOR_MODE = true; function isIn2DMode() { - return EXTERNALLY_MANAGED_2D_MINOR_MODE && Reticle.visible; + // In this version, we make our own determination of whether we're aimed a HUD element, + // because other scripts (such as handControllerPointer) might be using some other visualization + // instead of setting Reticle.visible. + return EXTERNALLY_MANAGED_2D_MINOR_MODE && (Reticle.pointingAtSystemOverlay || Overlays.getOverlayAtPoint(Reticle.position)); } function restore2DMode() { if (!EXTERNALLY_MANAGED_2D_MINOR_MODE) { From dfd03d5e6184ffb7a0f3b9a0b81e584dcf78e2a6 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Mon, 13 Jun 2016 17:15:51 -0700 Subject: [PATCH 45/93] Prepare for working laser. But at this point, we still show the laser AND the Reticle. --- .../system/controllers/handControllerPointer.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/scripts/system/controllers/handControllerPointer.js b/scripts/system/controllers/handControllerPointer.js index 1728647e5e..f5c0c4bb7c 100644 --- a/scripts/system/controllers/handControllerPointer.js +++ b/scripts/system/controllers/handControllerPointer.js @@ -319,6 +319,7 @@ function toggleHand() { // unequivocally switch which hand controls mouse positi activeTrigger = rightTrigger; activeHudLaser = RIGHT_HUD_LASER; } + clearSystemLaser(); } function makeToggleAction(hand) { // return a function(0|1) that makes the specified hand control mouse when 1 return function (on) { @@ -335,8 +336,8 @@ Script.scriptEnding.connect(clickMapping.disable); clickMapping.from(Controller.Standard.RT).peek().to(rightTrigger.triggerPress); clickMapping.from(Controller.Standard.LT).peek().to(leftTrigger.triggerPress); // Full smoothed trigger is a click. -clickMapping.from(rightTrigger.full).to(Controller.Actions.ReticleClick); -clickMapping.from(leftTrigger.full).to(Controller.Actions.ReticleClick); +clickMapping.from(rightTrigger.full).when(isPointingAtOverlay).to(Controller.Actions.ReticleClick); +clickMapping.from(leftTrigger.full).when(isPointingAtOverlay).to(Controller.Actions.ReticleClick); clickMapping.from(Controller.Standard.RightSecondaryThumb).peek().to(Controller.Actions.ContextMenu); clickMapping.from(Controller.Standard.LeftSecondaryThumb).peek().to(Controller.Actions.ContextMenu); // Partial smoothed trigger is activation. @@ -363,8 +364,8 @@ Script.scriptEnding.connect(function () { overlays.forEach(Overlays.deleteOverlay); }); var visualizationIsShowing = false; // Not whether it desired, but simply whether it is. Just an optimziation. -var systemLaserOn = false; var SYSTEM_LASER_DIRECTION = Vec3.normalize({x: 0, y: -1, z: -1}); // Guessing 45 degrees. +var systemLaserOn = false; function clearSystemLaser() { if (!systemLaserOn) { return; @@ -379,8 +380,12 @@ function turnOffVisualization(optionalEnableClicks) { // because we're showing c clearSystemLaser(); } else if (!systemLaserOn) { print('FIXME remove: setHandLasers', activeHudLaser, true, JSON.stringify(LASER_COLOR_XYZW), JSON.stringify(SYSTEM_LASER_DIRECTION)); - HMD.setHandLasers(activeHudLaser, true, LASER_COLOR_XYZW, SYSTEM_LASER_DIRECTION); + // If the active plugin doesn't implement hand lasers, show the mouse reticle instead. + /*Reticle.visible = !*/HMD.setHandLasers(activeHudLaser, true, LASER_COLOR_XYZW, SYSTEM_LASER_DIRECTION); + Reticle.visible = true; // FIXME: just for now, while hand lasers has the bug that requires this. systemLaserOn = true; + } else { + Reticle.visible = true; } if (!visualizationIsShowing) { return; @@ -415,9 +420,11 @@ function updateVisualization(controllerPosition, controllerDirection, hudPositio // For now, though, we present a false projection of the cursor onto whatever is below it. This is // different from the hand beam termination because the false projection is from the camera, while // the hand beam termination is from the hand. + /* // FIXME: We can tighten this up later, once we know what will and won't be included. var eye = Camera.getPosition(); var falseProjection = intersection3d(eye, Vec3.subtract(hudPosition3d, eye)); Overlays.editOverlay(fakeProjectionBall, {visible: true, position: falseProjection}); + */ Reticle.visible = false; return visualizationIsShowing; // In case we change caller to act conditionally. @@ -465,7 +472,6 @@ function update() { if (HMD.active) { // Doesn't hurt anything without the guard, but consider it documentation. Reticle.depth = SPHERICAL_HUD_DISTANCE; // NOT CORRECT IF WE SWITCH TO OFFSET SPHERE! } - Reticle.visible = true; return turnOffVisualization(true); } // We are not pointing at a HUD element (but it could be a 3d overlay). From 6707f889b82d2e8fdaa065ac32e4d536f24b8dd6 Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Mon, 13 Jun 2016 17:21:09 -0700 Subject: [PATCH 46/93] Fixing laser offset, support laser in Oculus --- .../display-plugins/OpenGLDisplayPlugin.cpp | 1 + .../src/display-plugins/OpenGLDisplayPlugin.h | 1 + .../display-plugins/hmd/HmdDisplayPlugin.cpp | 32 +++---- .../display-plugins/hmd/HmdDisplayPlugin.h | 4 +- .../oculus/src/OculusBaseDisplayPlugin.cpp | 24 ++++-- plugins/openvr/src/OpenVrDisplayPlugin.cpp | 26 ++++-- plugins/openvr/src/OpenVrHelpers.cpp | 86 ++++++++++++++++++- plugins/openvr/src/OpenVrHelpers.h | 4 + plugins/openvr/src/ViveControllerManager.cpp | 83 +----------------- 9 files changed, 138 insertions(+), 123 deletions(-) diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 363bde15e6..23c836b3de 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -517,6 +517,7 @@ void OpenGLDisplayPlugin::compositeLayers() { glBindTexture(GL_TEXTURE_2D, 0); Context::Disable(Capability::Blend); } + compositeExtra(); }); } diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h index 02a30a2570..69653b8c76 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -74,6 +74,7 @@ protected: virtual void compositeScene(); virtual void compositeOverlay(); virtual void compositePointer(); + virtual void compositeExtra() {}; virtual bool hasFocus() const override; diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index 9b71d3703b..9ae843d45d 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -316,10 +316,9 @@ void HmdDisplayPlugin::compositePointer() { Uniform(*_program, _mvpUniform).Set(mvp); _plane->Draw(); }); - - compositeLasers(); } + void HmdDisplayPlugin::internalPresent() { PROFILE_RANGE_EX(__FUNCTION__, 0xff00ff00, (uint64_t)presentCount()) @@ -412,24 +411,7 @@ bool HmdDisplayPlugin::setHandLaser(uint32_t hands, HandLaserMode mode, const ve return true; } -// FIXME try to consolidate the duplication of logic between this function and a similar one in CompsitorHelper. -static float calculateRayUiCollisionDistance(const glm::mat4& headPose, const glm::vec3& position, const glm::vec3& direction) { - auto relativePosition4 = glm::inverse(headPose) * vec4(position, 1); - auto relativePosition = vec3(relativePosition4) / relativePosition4.w; - auto relativeDirection = glm::inverse(glm::quat_cast(headPose)) * direction; - if (glm::abs(glm::length2(relativeDirection) - 1.0f) > EPSILON) { - relativeDirection = glm::normalize(relativeDirection); - } - // FIXME fetch the actual UI radius from... somewhere? - float uiRadius = 1.0f; - float instersectionDistance; - if (!glm::intersectRaySphere(relativePosition, relativeDirection, vec3(0), uiRadius * uiRadius, instersectionDistance)) { - return -1; - } - return instersectionDistance; -} - -void HmdDisplayPlugin::compositeLasers() { +void HmdDisplayPlugin::compositeExtra() { std::array handLasers; std::array renderHandPoses; withPresentThreadLock([&] { @@ -465,10 +447,16 @@ void HmdDisplayPlugin::compositeLasers() { const auto& laserDirection = handLaser.direction; auto model = renderHandPoses[i]; auto castDirection = glm::quat_cast(model) * laserDirection; + if (glm::abs(glm::length2(castDirection) - 1.0f) > EPSILON) { + castDirection = glm::normalize(castDirection); + } + + // FIXME fetch the actual UI radius from... somewhere? + float uiRadius = 1.0f; // Find the intersection of the laser with he UI and use it to scale the model matrix - float distance = calculateRayUiCollisionDistance(_currentPresentFrameInfo.presentPose, vec3(renderHandPoses[i][3]), castDirection); - if (distance < 0) { + float distance; + if (!glm::intersectRaySphere(vec3(renderHandPoses[i][3]), castDirection, vec3(0), uiRadius * uiRadius, distance)) { continue; } diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h index 7cdcf06e9a..738028d684 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h @@ -46,9 +46,7 @@ protected: void customizeContext() override; void uncustomizeContext() override; void updateFrameData() override; - - void compositeLasers(); - + void compositeExtra() override; struct HandLaserInfo { HandLaserMode mode { HandLaserMode::None }; diff --git a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp index a16f630bf8..61c4696f51 100644 --- a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp @@ -8,6 +8,7 @@ #include "OculusBaseDisplayPlugin.h" #include +#include #include "OculusHelpers.h" @@ -25,16 +26,21 @@ bool OculusBaseDisplayPlugin::beginFrameRender(uint32_t frameIndex) { _currentRenderFrameInfo.renderPose = toGlm(trackingState.HeadPose.ThePose); _currentRenderFrameInfo.presentPose = _currentRenderFrameInfo.renderPose; + std::array handPoses; + // Make controller poses available to the presentation thread + ovr_for_each_hand([&](ovrHandType hand) { + static const auto REQUIRED_HAND_STATUS = ovrStatus_OrientationTracked & ovrStatus_PositionTracked; + if (REQUIRED_HAND_STATUS != (trackingState.HandStatusFlags[hand] & REQUIRED_HAND_STATUS)) { + return; + } + + auto correctedPose = ovrControllerPoseToHandPose(hand, trackingState.HandPoses[hand]); + static const glm::quat HAND_TO_LASER_ROTATION = glm::rotation(Vectors::UNIT_Z, Vectors::UNIT_NEG_Y); + handPoses[hand] = glm::translate(glm::mat4(), correctedPose.translation) * glm::mat4_cast(correctedPose.rotation * HAND_TO_LASER_ROTATION); + }); + withRenderThreadLock([&] { - // Make controller poses available to the presentation thread - ovr_for_each_hand([&](ovrHandType hand){ - static const auto REQUIRED_HAND_STATUS = ovrStatus_OrientationTracked & ovrStatus_PositionTracked; - if (REQUIRED_HAND_STATUS == (trackingState.HandStatusFlags[hand] & REQUIRED_HAND_STATUS)) { - _handPoses[hand] = toGlm(trackingState.HandPoses[hand].ThePose); - } else { - _handPoses[hand] = glm::mat4(); - } - }); + _handPoses = handPoses; _frameInfos[frameIndex] = _currentRenderFrameInfo; }); return true; diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index 92c01dc0a3..d57dddf512 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -179,15 +180,26 @@ bool OpenVrDisplayPlugin::beginFrameRender(uint32_t frameIndex) { _currentRenderFrameInfo.renderPose = _trackedDevicePoseMat4[vr::k_unTrackedDeviceIndex_Hmd]; bool keyboardVisible = isOpenVrKeyboardShown(); + + std::array handPoses; + if (!keyboardVisible) { + for (int i = 0; i < 2; ++i) { + if (handIndices[i] == vr::k_unTrackedDeviceIndexInvalid) { + continue; + } + auto deviceIndex = handIndices[i]; + const mat4& mat = _trackedDevicePoseMat4[deviceIndex]; + const vec3& linearVelocity = _trackedDeviceLinearVelocities[deviceIndex]; + const vec3& angularVelocity = _trackedDeviceAngularVelocities[deviceIndex]; + auto correctedPose = openVrControllerPoseToHandPose(i == 0, mat, linearVelocity, angularVelocity); + static const glm::quat HAND_TO_LASER_ROTATION = glm::rotation(Vectors::UNIT_Z, Vectors::UNIT_NEG_Y); + handPoses[i] = glm::translate(glm::mat4(), correctedPose.translation) * glm::mat4_cast(correctedPose.rotation * HAND_TO_LASER_ROTATION); + } + } + withRenderThreadLock([&] { // Make controller poses available to the presentation thread - for (int i = 0; i < 2; ++i) { - if (keyboardVisible || handIndices[i] == vr::k_unTrackedDeviceIndexInvalid) { - _handPoses[i] = glm::mat4(); - } else { - _handPoses[i] = _sensorResetMat * toGlm(_trackedDevicePose[handIndices[i]].mDeviceToAbsoluteTracking); - } - } + _handPoses = handPoses; _frameInfos[frameIndex] = _currentRenderFrameInfo; }); return true; diff --git a/plugins/openvr/src/OpenVrHelpers.cpp b/plugins/openvr/src/OpenVrHelpers.cpp index e4cca6ecd6..4b4eef0538 100644 --- a/plugins/openvr/src/OpenVrHelpers.cpp +++ b/plugins/openvr/src/OpenVrHelpers.cpp @@ -18,8 +18,9 @@ #include #include - #include +#include +#include Q_DECLARE_LOGGING_CATEGORY(displayplugins) Q_LOGGING_CATEGORY(displayplugins, "hifi.plugins.display") @@ -242,3 +243,86 @@ void handleOpenVrEvents() { } +controller::Pose openVrControllerPoseToHandPose(bool isLeftHand, const mat4& mat, const vec3& linearVelocity, const vec3& angularVelocity) { + // When the sensor-to-world rotation is identity the coordinate axes look like this: + // + // user + // forward + // -z + // | + // y| user + // y o----x right + // o-----x user + // | up + // | + // z + // + // Rift + + // From ABOVE the hand canonical axes looks like this: + // + // | | | | y | | | | + // | | | | | | | | | + // | | | | | + // |left | / x---- + \ |right| + // | _/ z \_ | + // | | | | + // | | | | + // + + // So when the user is in Rift space facing the -zAxis with hands outstretched and palms down + // the rotation to align the Touch axes with those of the hands is: + // + // touchToHand = halfTurnAboutY * quaterTurnAboutX + + // Due to how the Touch controllers fit into the palm there is an offset that is different for each hand. + // You can think of this offset as the inverse of the measured rotation when the hands are posed, such that + // the combination (measurement * offset) is identity at this orientation. + // + // Qoffset = glm::inverse(deltaRotation when hand is posed fingers forward, palm down) + // + // An approximate offset for the Touch can be obtained by inspection: + // + // Qoffset = glm::inverse(glm::angleAxis(sign * PI/2.0f, zAxis) * glm::angleAxis(PI/4.0f, xAxis)) + // + // So the full equation is: + // + // Q = combinedMeasurement * touchToHand + // + // Q = (deltaQ * QOffset) * (yFlip * quarterTurnAboutX) + // + // Q = (deltaQ * inverse(deltaQForAlignedHand)) * (yFlip * quarterTurnAboutX) + static const glm::quat yFlip = glm::angleAxis(PI, Vectors::UNIT_Y); + static const glm::quat quarterX = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_X); + static const glm::quat touchToHand = yFlip * quarterX; + + static const glm::quat leftQuarterZ = glm::angleAxis(-PI_OVER_TWO, Vectors::UNIT_Z); + static const glm::quat rightQuarterZ = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_Z); + static const glm::quat eighthX = glm::angleAxis(PI / 4.0f, Vectors::UNIT_X); + + static const glm::quat leftRotationOffset = glm::inverse(leftQuarterZ * eighthX) * touchToHand; + static const glm::quat rightRotationOffset = glm::inverse(rightQuarterZ * eighthX) * touchToHand; + + static const float CONTROLLER_LENGTH_OFFSET = 0.0762f; // three inches + static const glm::vec3 CONTROLLER_OFFSET = glm::vec3(CONTROLLER_LENGTH_OFFSET / 2.0f, + CONTROLLER_LENGTH_OFFSET / 2.0f, + CONTROLLER_LENGTH_OFFSET * 2.0f); + static const glm::vec3 leftTranslationOffset = glm::vec3(-1.0f, 1.0f, 1.0f) * CONTROLLER_OFFSET; + static const glm::vec3 rightTranslationOffset = CONTROLLER_OFFSET; + + auto translationOffset = (isLeftHand ? leftTranslationOffset : rightTranslationOffset); + auto rotationOffset = (isLeftHand ? leftRotationOffset : rightRotationOffset); + + glm::vec3 position = extractTranslation(mat); + glm::quat rotation = glm::normalize(glm::quat_cast(mat)); + + position += rotation * translationOffset; + rotation = rotation * rotationOffset; + + // transform into avatar frame + auto result = controller::Pose(position, rotation); + // handle change in velocity due to translationOffset + result.velocity = linearVelocity + glm::cross(angularVelocity, position - extractTranslation(mat)); + result.angularVelocity = angularVelocity; + return result; +} \ No newline at end of file diff --git a/plugins/openvr/src/OpenVrHelpers.h b/plugins/openvr/src/OpenVrHelpers.h index db8bb4f2e8..131db517ab 100644 --- a/plugins/openvr/src/OpenVrHelpers.h +++ b/plugins/openvr/src/OpenVrHelpers.h @@ -12,6 +12,8 @@ #include #include +#include + bool openVrSupported(); vr::IVRSystem* acquireOpenVrSystem(); @@ -55,3 +57,5 @@ inline vr::HmdMatrix34_t toOpenVr(const mat4& m) { } return result; } + +controller::Pose openVrControllerPoseToHandPose(bool isLeftHand, const mat4& mat, const vec3& linearVelocity, const vec3& angularVelocity); diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 4e17a2edf4..83b72c5c08 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -37,10 +37,6 @@ vr::IVRSystem* acquireOpenVrSystem(); void releaseOpenVrSystem(); -static const float CONTROLLER_LENGTH_OFFSET = 0.0762f; // three inches -static const glm::vec3 CONTROLLER_OFFSET = glm::vec3(CONTROLLER_LENGTH_OFFSET / 2.0f, - CONTROLLER_LENGTH_OFFSET / 2.0f, - CONTROLLER_LENGTH_OFFSET * 2.0f); static const char* CONTROLLER_MODEL_STRING = "vr_controller_05_wireless_b"; static const QString MENU_PARENT = "Avatar"; @@ -382,86 +378,11 @@ void ViveControllerManager::InputDevice::handleButtonEvent(float deltaTime, uint void ViveControllerManager::InputDevice::handlePoseEvent(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, const mat4& mat, const vec3& linearVelocity, const vec3& angularVelocity, bool isLeftHand) { - // When the sensor-to-world rotation is identity the coordinate axes look like this: - // - // user - // forward - // -z - // | - // y| user - // y o----x right - // o-----x user - // | up - // | - // z - // - // Vive - // - - // From ABOVE the hand canonical axes looks like this: - // - // | | | | y | | | | - // | | | | | | | | | - // | | | | | - // |left | / x---- + \ |right| - // | _/ z \_ | - // | | | | - // | | | | - // - - // So when the user is standing in Vive space facing the -zAxis with hands outstretched and palms down - // the rotation to align the Vive axes with those of the hands is: - // - // QviveToHand = halfTurnAboutY * quaterTurnAboutX - - // Due to how the Vive controllers fit into the palm there is an offset that is different for each hand. - // You can think of this offset as the inverse of the measured rotation when the hands are posed, such that - // the combination (measurement * offset) is identity at this orientation. - // - // Qoffset = glm::inverse(deltaRotation when hand is posed fingers forward, palm down) - // - // An approximate offset for the Vive can be obtained by inspection: - // - // Qoffset = glm::inverse(glm::angleAxis(sign * PI/4.0f, zAxis) * glm::angleAxis(PI/2.0f, xAxis)) - // - // So the full equation is: - // - // Q = combinedMeasurement * viveToHand - // - // Q = (deltaQ * QOffset) * (yFlip * quarterTurnAboutX) - // - // Q = (deltaQ * inverse(deltaQForAlignedHand)) * (yFlip * quarterTurnAboutX) - - static const glm::quat yFlip = glm::angleAxis(PI, Vectors::UNIT_Y); - static const glm::quat quarterX = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_X); - static const glm::quat viveToHand = yFlip * quarterX; - - static const glm::quat leftQuaterZ = glm::angleAxis(-PI_OVER_TWO, Vectors::UNIT_Z); - static const glm::quat rightQuaterZ = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_Z); - static const glm::quat eighthX = glm::angleAxis(PI / 4.0f, Vectors::UNIT_X); - - static const glm::quat leftRotationOffset = glm::inverse(leftQuaterZ * eighthX) * viveToHand; - static const glm::quat rightRotationOffset = glm::inverse(rightQuaterZ * eighthX) * viveToHand; - - static const glm::vec3 leftTranslationOffset = glm::vec3(-1.0f, 1.0f, 1.0f) * CONTROLLER_OFFSET; - static const glm::vec3 rightTranslationOffset = CONTROLLER_OFFSET; - - auto translationOffset = (isLeftHand ? leftTranslationOffset : rightTranslationOffset); - auto rotationOffset = (isLeftHand ? leftRotationOffset : rightRotationOffset); - - glm::vec3 position = extractTranslation(mat); - glm::quat rotation = glm::normalize(glm::quat_cast(mat)); - - position += rotation * translationOffset; - rotation = rotation * rotationOffset; + auto pose = openVrControllerPoseToHandPose(isLeftHand, mat, linearVelocity, angularVelocity); // transform into avatar frame glm::mat4 controllerToAvatar = glm::inverse(inputCalibrationData.avatarMat) * inputCalibrationData.sensorToWorldMat; - auto avatarPose = controller::Pose(position, rotation); - // handle change in velocity due to translationOffset - avatarPose.velocity = linearVelocity + glm::cross(angularVelocity, position - extractTranslation(mat)); - avatarPose.angularVelocity = angularVelocity; - _poseStateMap[isLeftHand ? controller::LEFT_HAND : controller::RIGHT_HAND] = avatarPose.transform(controllerToAvatar); + _poseStateMap[isLeftHand ? controller::LEFT_HAND : controller::RIGHT_HAND] = pose.transform(controllerToAvatar); } bool ViveControllerManager::InputDevice::triggerHapticPulse(float strength, float duration, controller::Hand hand) { From 28b6cc2777a577474a027ace28b350e948bf70b3 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 13 Jun 2016 18:54:21 -0700 Subject: [PATCH 47/93] handControllerGrab.js is now eslint clean. --- .../system/controllers/handControllerGrab.js | 127 ++++++------------ 1 file changed, 43 insertions(+), 84 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 2857c91936..34d4ca6294 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -10,7 +10,7 @@ // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -/*global print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt, pointInExtents, vec3equal, setEntityCustomData, getEntityCustomData */ +/*global print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, Reticle, Messages, setEntityCustomData, getEntityCustomData, vec3toStr */ Script.include("/~/system/libraries/utils.js"); @@ -47,7 +47,6 @@ var DISTANCE_HOLDING_RADIUS_FACTOR = 3.5; // multiplied by distance between hand var DISTANCE_HOLDING_ACTION_TIMEFRAME = 0.1; // how quickly objects move to their new position var DISTANCE_HOLDING_UNITY_MASS = 1200; // The mass at which the distance holding action timeframe is unmodified var DISTANCE_HOLDING_UNITY_DISTANCE = 6; // The distance at which the distance holding action timeframe is unmodified -var DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR = 2.0; // object rotates this much more than hand did var MOVE_WITH_HEAD = true; // experimental head-control of distantly held objects var FAR_TO_NEAR_GRAB_PADDING_FACTOR = 1.2; @@ -81,13 +80,6 @@ var NEAR_GRABBING_KINEMATIC = true; // force objects to be kinematic when near-g var SHOW_GRAB_SPHERE = false; // draw a green sphere to show the grab search position and size var CHECK_TOO_FAR_UNEQUIP_TIME = 1.0; // seconds -// -// equip -// - -var EQUIP_SPRING_SHUTOFF_DISTANCE = 0.05; -var EQUIP_SPRING_TIMEFRAME = 0.4; // how quickly objects move to their new position - // // other constants // @@ -158,7 +150,7 @@ var STATE_SEARCHING = 1; var STATE_HOLD_SEARCHING = 2; var STATE_DISTANCE_HOLDING = 3; var STATE_NEAR_GRABBING = 4; -var STATE_NEAR_TRIGGER = 4; +var STATE_NEAR_TRIGGER = 5; var STATE_CONTINUE_NEAR_TRIGGER = 6; var STATE_FAR_TRIGGER = 7; var STATE_CONTINUE_FAR_TRIGGER = 8; @@ -200,7 +192,7 @@ CONTROLLER_STATE_MACHINE[STATE_NEAR_GRABBING] = { updateMethod: "nearGrabbing" }; CONTROLLER_STATE_MACHINE[STATE_HOLD] = { - name: "continue_hold", + name: "hold", enterMethod: "nearGrabbingEnter", updateMethod: "nearGrabbing" }; @@ -505,7 +497,7 @@ function MyController(hand) { this.overlayLine = Overlays.addOverlay("line3d", lineProperties); } else { - var success = Overlays.editOverlay(this.overlayLine, { + Overlays.editOverlay(this.overlayLine, { lineWidth: 5, start: closePoint, end: farPoint, @@ -621,13 +613,6 @@ function MyController(hand) { }) }; - this.renewParticleBeamLifetime = function() { - var props = Entities.getEntityProperties(this.particleBeamObject, "age"); - Entities.editEntity(this.particleBeamObject, { - lifetime: TEMPORARY_PARTICLE_BEAM_LIFETIME + props.age // renew lifetime - }) - } - this.evalLightWorldTransform = function(modelPos, modelRot) { var MODEL_LIGHT_POSITION = { @@ -648,7 +633,7 @@ function MyController(hand) { }; }; - this.handleSpotlight = function(parentID, position) { + this.handleSpotlight = function(parentID) { var LIFETIME = 100; var modelProperties = Entities.getEntityProperties(parentID, ['position', 'rotation']); @@ -672,7 +657,7 @@ function MyController(hand) { exponent: 0.3, cutoff: 20, lifetime: LIFETIME, - position: lightTransform.p, + position: lightTransform.p }; if (this.spotlight === null) { @@ -680,12 +665,12 @@ function MyController(hand) { } else { Entities.editEntity(this.spotlight, { //without this, this light would maintain rotation with its parent - rotation: Quat.fromPitchYawRollDegrees(-90, 0, 0), + rotation: Quat.fromPitchYawRollDegrees(-90, 0, 0) }) } }; - this.handlePointLight = function(parentID, position) { + this.handlePointLight = function(parentID) { var LIFETIME = 100; var modelProperties = Entities.getEntityProperties(parentID, ['position', 'rotation']); @@ -709,13 +694,11 @@ function MyController(hand) { exponent: 0.3, cutoff: 20, lifetime: LIFETIME, - position: lightTransform.p, + position: lightTransform.p }; if (this.pointlight === null) { this.pointlight = Entities.addEntity(lightProperties); - } else { - } }; @@ -915,12 +898,9 @@ function MyController(hand) { } var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; - var currentHandRotation = Controller.getPoseValue(controllerHandInput).rotation; var currentControllerPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, Controller.getPoseValue(controllerHandInput).translation), MyAvatar.position); - var handDeltaRotation = Quat.multiply(currentHandRotation, Quat.inverse(this.startingHandRotation)); - var avatarControllerPose = Controller.getPoseValue((this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand); var controllerRotation = Quat.multiply(MyAvatar.orientation, avatarControllerPose.rotation); @@ -941,7 +921,7 @@ function MyController(hand) { this.lastPickTime = now; } - rayPickedCandidateEntities = []; // the list of candidates to consider grabbing + var rayPickedCandidateEntities = []; // the list of candidates to consider grabbing this.intersectionDistance = 0.0; for (var index = 0; index < pickRays.length; ++index) { @@ -974,8 +954,8 @@ function MyController(hand) { } } - nearPickedCandidateEntities = Entities.findEntities(handPosition, GRAB_RADIUS); - candidateEntities = rayPickedCandidateEntities.concat(nearPickedCandidateEntities); + var nearPickedCandidateEntities = Entities.findEntities(handPosition, GRAB_RADIUS); + var candidateEntities = rayPickedCandidateEntities.concat(nearPickedCandidateEntities); var forbiddenNames = ["Grab Debug Entity", "grab pointer"]; var forbiddenTypes = ['Unknown', 'Light', 'PolyLine', 'Zone']; @@ -990,9 +970,9 @@ function MyController(hand) { var propsForCandidate = Entities.getEntityProperties(candidateEntities[i], GRABBABLE_PROPERTIES); var near = (nearPickedCandidateEntities.indexOf(candidateEntities[i]) >= 0); - var isPhysical = propsArePhysical(propsForCandidate); + var physical = propsArePhysical(propsForCandidate); var grabbable; - if (isPhysical) { + if (physical) { // physical things default to grabbable grabbable = true; } else { @@ -1051,7 +1031,7 @@ function MyController(hand) { } if (this.state == STATE_SEARCHING && - !isPhysical && distance > NEAR_PICK_MAX_DISTANCE && !near && !grabbableDataForCandidate.wantsTrigger) { + !physical && distance > NEAR_PICK_MAX_DISTANCE && !near && !grabbableDataForCandidate.wantsTrigger) { // we can't distance-grab non-physical if (WANT_DEBUG_SEARCH_NAME && propsForCandidate.name == WANT_DEBUG_SEARCH_NAME) { print("grab is skipping '" + WANT_DEBUG_SEARCH_NAME + "': not physical and too far for near-grab"); @@ -1068,18 +1048,18 @@ function MyController(hand) { } if ((this.grabbedEntity !== null) && (this.triggerSmoothedGrab() || this.secondarySqueezed())) { // We are squeezing enough to grab, and we've found an entity that we'll try to do something with. - var near = (nearPickedCandidateEntities.indexOf(this.grabbedEntity) >= 0) || minDistance <= NEAR_PICK_MAX_DISTANCE; + var isNear = (nearPickedCandidateEntities.indexOf(this.grabbedEntity) >= 0) || minDistance <= NEAR_PICK_MAX_DISTANCE; var isPhysical = propsArePhysical(props); // near or far trigger if (grabbableData.wantsTrigger) { - this.setState(near ? STATE_NEAR_TRIGGER : STATE_FAR_TRIGGER); + this.setState(isNear ? STATE_NEAR_TRIGGER : STATE_FAR_TRIGGER); return; } // near grab with action or equip var grabData = getEntityCustomData(GRAB_USER_DATA_KEY, this.grabbedEntity, {}); var refCount = ("refCount" in grabData) ? grabData.refCount : 0; - if (near && (refCount < 1 || entityHasActions(this.grabbedEntity))) { + if (isNear && (refCount < 1 || entityHasActions(this.grabbedEntity))) { if (this.state == STATE_SEARCHING) { this.setState(STATE_NEAR_GRABBING); } else { // (this.state == STATE_HOLD_SEARCHING) @@ -1092,7 +1072,7 @@ function MyController(hand) { return; } // far grab - if (isPhysical && !near) { + if (isPhysical && !isNear) { if (entityIsGrabbedByOther(this.grabbedEntity)) { // don't distance grab something that is already grabbed. if (WANT_DEBUG_SEARCH_NAME && props.name == WANT_DEBUG_SEARCH_NAME) { @@ -1142,9 +1122,6 @@ function MyController(hand) { } return; } - if (WANT_DEBUG_SEARCH_NAME && props.name == WANT_DEBUG_SEARCH_NAME) { - print("grab is skipping '" + WANT_DEBUG_SEARCH_NAME + "': fell through."); - } } //search line visualizations @@ -1266,10 +1243,13 @@ function MyController(hand) { radius); // double delta controller rotation + /* + var DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR = 2.0; // object rotates this much more than hand did var handChange = Quat.multiply(Quat.slerp(this.previousControllerRotation, controllerRotation, DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR), Quat.inverse(this.previousControllerRotation)); + */ // update the currentObject position and rotation. this.currentObjectPosition = Vec3.sum(this.currentObjectPosition, handMoved); @@ -1323,9 +1303,10 @@ function MyController(hand) { } } + /* var defaultConstraintData = { axisStart: false, - axisEnd: false, + axisEnd: false } var constraintData = getEntityCustomData('lightModifierKey', this.grabbedEntity, defaultConstraintData); @@ -1343,6 +1324,7 @@ function MyController(hand) { z: this.currentObjectPosition.z } } + */ var handPosition = this.getHandPosition(); @@ -1448,7 +1430,6 @@ function MyController(hand) { } this.nearGrabbingEnter = function() { - var now = Date.now(); this.lineOff(); this.overlayLineOff(); @@ -1503,7 +1484,7 @@ function MyController(hand) { // grab entity via parenting this.actionID = null; var handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"); - reparentProps = { + var reparentProps = { parentID: MyAvatar.sessionUUID, parentJointIndex: handJointIndex } @@ -1573,7 +1554,7 @@ function MyController(hand) { var handPosition = this.getHandPosition(); // the center of the equipped object being far from the hand isn't enough to autoequip -- we also // need to fail the findEntities test. - nearPickedCandidateEntities = Entities.findEntities(handPosition, GRAB_RADIUS); + var nearPickedCandidateEntities = Entities.findEntities(handPosition, GRAB_RADIUS); if (nearPickedCandidateEntities.indexOf(this.grabbedEntity) == -1) { // for whatever reason, the held/equipped entity has been pulled away. ungrab or unequip. print("handControllerGrab -- autoreleasing held or equipped item because it is far from hand." + @@ -1597,9 +1578,7 @@ function MyController(hand) { // from the palm. var handControllerPosition = (this.hand === RIGHT_HAND) ? MyAvatar.rightHandPosition : MyAvatar.leftHandPosition; - var now = Date.now(); - var deltaPosition = Vec3.subtract(handControllerPosition, this.currentHandControllerTipPosition); // meters var deltaTime = (now - this.currentObjectTime) / MSECS_PER_SEC; // convert to seconds if (deltaTime > 0.0) { @@ -1619,7 +1598,6 @@ function MyController(hand) { this.currentHandControllerTipPosition = handControllerPosition; this.currentObjectTime = now; - var grabData = getEntityCustomData(GRAB_USER_DATA_KEY, this.grabbedEntity, {}); if (this.state === STATE_HOLD) { this.callEntityMethodOnGrabbed("continueEquip"); } @@ -1843,6 +1821,8 @@ function MyController(hand) { } this.deactivateEntity = function(entityID, noVelocity) { + var deactiveProps; + if (!this.entityActivated) { return; } @@ -1852,7 +1832,7 @@ function MyController(hand) { if (data && data["refCount"]) { data["refCount"] = data["refCount"] - 1; if (data["refCount"] < 1) { - var deactiveProps = { + deactiveProps = { gravity: data["gravity"], collidesWith: data["collidesWith"], collisionless: data["collisionless"], @@ -1865,7 +1845,6 @@ function MyController(hand) { // it looks like the dropped thing should fall, give it a little velocity. var props = Entities.getEntityProperties(entityID, ["parentID", "velocity", "dynamic", "shapeType"]) var parentID = props.parentID; - var forceVelocity = false; var doSetVelocity = false; if (parentID != NULL_UUID && deactiveProps.parentID == NULL_UUID && propsArePhysical(props)) { @@ -1900,7 +1879,7 @@ function MyController(hand) { // the parent causes it to go off in the wrong direction. This is a bug that should // be fixed. Entities.editEntity(entityID, { - velocity: this.currentVelocity, + velocity: this.currentVelocity // angularVelocity: this.currentAngularVelocity }); } @@ -1908,7 +1887,7 @@ function MyController(hand) { data = null; } else if (this.shouldResetParentOnRelease) { // we parent-grabbed this from another parent grab. try to put it back where we found it. - var deactiveProps = { + deactiveProps = { parentID: this.previousParentID, parentJointIndex: this.previousParentJointIndex, velocity: {x: 0.0, y: 0.0, z: 0.0}, @@ -1925,32 +1904,7 @@ function MyController(hand) { } setEntityCustomData(GRAB_USER_DATA_KEY, entityID, data); }; - - // AJT: WTF TO DO WITH THIS? - /* - this.checkNewlyLoaded = function(loadedEntityID) { - if (this.state == STATE_OFF || - this.state == STATE_SEARCHING || - this.state == STATE_HOLD_SEARCHING) { - var loadedProps = Entities.getEntityProperties(loadedEntityID); - if (loadedProps.parentID != MyAvatar.sessionUUID) { - return; - } - var handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"); - if (loadedProps.parentJointIndex != handJointIndex) { - return; - } - print("--- handControllerGrab found loaded entity ---"); - // an entity has been loaded and it's where this script would have equipped something, so switch states. - this.grabbedEntity = loadedEntityID; - this.activateEntity(this.grabbedEntity, loadedProps, true); - this.isInitialGrab = true; - this.callEntityMethodOnGrabbed("startEquip"); - this.setState(STATE_EQUIP); - } - } - */ -}; +} var rightController = new MyController(RIGHT_HAND); var leftController = new MyController(LEFT_HAND); @@ -1988,7 +1942,8 @@ Messages.subscribe('Hifi-Hand-Grab'); Messages.subscribe('Hifi-Hand-RayPick-Blacklist'); Messages.subscribe('Hifi-Object-Manipulation'); -handleHandMessages = function(channel, message, sender) { +var handleHandMessages = function(channel, message, sender) { + var data; if (sender === MyAvatar.sessionUUID) { if (channel === 'Hifi-Hand-Disabler') { if (message === 'left') { @@ -2002,17 +1957,19 @@ handleHandMessages = function(channel, message, sender) { } } else if (channel === 'Hifi-Hand-Grab') { try { - var data = JSON.parse(message); + data = JSON.parse(message); var selectedController = (data.hand === 'left') ? leftController : rightController; selectedController.release(); selectedController.setState(STATE_HOLD); selectedController.grabbedEntity = data.entityID; - } catch (e) {} + } catch (e) { + print("WARNING: error parsing Hifi-Hand-Grab message"); + } } else if (channel === 'Hifi-Hand-RayPick-Blacklist') { try { - var data = JSON.parse(message); + data = JSON.parse(message); var action = data.action; var id = data.id; var index = blacklist.indexOf(id); @@ -2026,7 +1983,9 @@ handleHandMessages = function(channel, message, sender) { } } - } catch (e) {} + } catch (e) { + print("WARNING: error parsing Hifi-Hand-RayPick-Blacklist message"); + } } } } From f11735550fbeae426aa0f2675c80764a95d04b94 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 13 Jun 2016 18:55:53 -0700 Subject: [PATCH 48/93] Fix for Vive RIGHT_GRIP LEFT_GRIP button never becoming zero. --- plugins/openvr/src/ViveControllerManager.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 4e17a2edf4..97c502454b 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -370,6 +370,10 @@ void ViveControllerManager::InputDevice::handleButtonEvent(float deltaTime, uint } else if (button == vr::k_EButton_SteamVR_Touchpad) { _buttonPressedMap.insert(isLeftHand ? LS : RS); } + } else { + if (button == vr::k_EButton_Grip) { + _axisStateMap[isLeftHand ? LEFT_GRIP : RIGHT_GRIP] = 0.0f; + } } if (touched) { From cb51d00c1d50090d43a1ae03eb236f3f8e2a8fdc Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 13 Jun 2016 19:08:33 -0700 Subject: [PATCH 49/93] Removed CONTINUE_FAR_TRIGGER and CONTINUE_NEAR_TRIGGER --- .../system/controllers/handControllerGrab.js | 38 +++++-------------- 1 file changed, 9 insertions(+), 29 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 34d4ca6294..6daafa425f 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -151,10 +151,8 @@ var STATE_HOLD_SEARCHING = 2; var STATE_DISTANCE_HOLDING = 3; var STATE_NEAR_GRABBING = 4; var STATE_NEAR_TRIGGER = 5; -var STATE_CONTINUE_NEAR_TRIGGER = 6; -var STATE_FAR_TRIGGER = 7; -var STATE_CONTINUE_FAR_TRIGGER = 8; -var STATE_HOLD = 9; +var STATE_FAR_TRIGGER = 6; +var STATE_HOLD = 7; // "collidesWith" is specified by comma-separated list of group names // the possible group names are: static, dynamic, kinematic, myAvatar, otherAvatar @@ -197,21 +195,15 @@ CONTROLLER_STATE_MACHINE[STATE_HOLD] = { updateMethod: "nearGrabbing" }; CONTROLLER_STATE_MACHINE[STATE_NEAR_TRIGGER] = { - name: "near_trigger", + name: "trigger", + enterMethod: "nearTriggerEnter", updateMethod: "nearTrigger" }; -CONTROLLER_STATE_MACHINE[STATE_CONTINUE_NEAR_TRIGGER] = { - name: "continue_near_trigger", - updateMethod: "continueNearTrigger" -}; CONTROLLER_STATE_MACHINE[STATE_FAR_TRIGGER] = { name: "far_trigger", + enterMethod: "farTriggerEnter", updateMethod: "farTrigger" }; -CONTROLLER_STATE_MACHINE[STATE_CONTINUE_FAR_TRIGGER] = { - name: "continue_far_trigger", - updateMethod: "continueFarTrigger" -}; function stateToName(state) { return CONTROLLER_STATE_MACHINE[state] ? CONTROLLER_STATE_MACHINE[state].name : "???"; @@ -1627,27 +1619,15 @@ function MyController(hand) { } }; - this.nearTrigger = function() { - if (this.triggerSmoothedReleased() && this.secondaryReleased()) { - this.setState(STATE_OFF); - this.callEntityMethodOnGrabbed("stopNearTrigger"); - return; - } + this.nearTriggerEnter = function() { this.callEntityMethodOnGrabbed("startNearTrigger"); - this.setState(STATE_CONTINUE_NEAR_TRIGGER); }; - this.farTrigger = function() { - if (this.triggerSmoothedReleased() && this.secondaryReleased()) { - this.setState(STATE_OFF); - this.callEntityMethodOnGrabbed("stopFarTrigger"); - return; - } + this.farTriggerEnter = function() { this.callEntityMethodOnGrabbed("startFarTrigger"); - this.setState(STATE_CONTINUE_FAR_TRIGGER); }; - this.continueNearTrigger = function() { + this.nearTrigger = function() { if (this.triggerSmoothedReleased() && this.secondaryReleased()) { this.setState(STATE_OFF); this.callEntityMethodOnGrabbed("stopNearTrigger"); @@ -1656,7 +1636,7 @@ function MyController(hand) { this.callEntityMethodOnGrabbed("continueNearTrigger"); }; - this.continueFarTrigger = function() { + this.farTrigger = function() { if (this.triggerSmoothedReleased() && this.secondaryReleased()) { this.setState(STATE_OFF); this.callEntityMethodOnGrabbed("stopFarTrigger"); From f3c47acbb240871a9889606fe1b0f6127dd3f160 Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Mon, 13 Jun 2016 18:20:55 -0700 Subject: [PATCH 50/93] Fix offset when UI transform is not identity --- .../src/display-plugins/hmd/HmdDisplayPlugin.cpp | 4 +++- .../src/display-plugins/hmd/HmdDisplayPlugin.h | 2 ++ plugins/oculus/src/OculusBaseDisplayPlugin.cpp | 2 ++ plugins/openvr/src/OpenVrDisplayPlugin.cpp | 2 ++ 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index 698e19fcab..6801d94f2f 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -415,9 +415,11 @@ bool HmdDisplayPlugin::setHandLaser(uint32_t hands, HandLaserMode mode, const ve void HmdDisplayPlugin::compositeExtra() { std::array handLasers; std::array renderHandPoses; + Transform uiModelTransform; withPresentThreadLock([&] { handLasers = _handLasers; renderHandPoses = _handPoses; + uiModelTransform = _uiModelTransform; }); // If neither hand laser is activated, exit @@ -457,7 +459,7 @@ void HmdDisplayPlugin::compositeExtra() { // Find the intersection of the laser with he UI and use it to scale the model matrix float distance; - if (!glm::intersectRaySphere(vec3(renderHandPoses[i][3]), castDirection, vec3(0), uiRadius * uiRadius, distance)) { + if (!glm::intersectRaySphere(vec3(renderHandPoses[i][3]), castDirection, uiModelTransform.getTranslation(), uiRadius * uiRadius, distance)) { continue; } diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h index 738028d684..fada15d864 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h @@ -10,6 +10,7 @@ #include #include +#include #include "../OpenGLDisplayPlugin.h" @@ -59,6 +60,7 @@ protected: } }; + Transform _uiModelTransform; std::array _handLasers; std::array _handPoses; std::array _eyeOffsets; diff --git a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp index 61c4696f51..e188bea52e 100644 --- a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp @@ -9,6 +9,7 @@ #include #include +#include #include "OculusHelpers.h" @@ -40,6 +41,7 @@ bool OculusBaseDisplayPlugin::beginFrameRender(uint32_t frameIndex) { }); withRenderThreadLock([&] { + _uiModelTransform = DependencyManager::get()->getModelTransform(); _handPoses = handPoses; _frameInfos[frameIndex] = _currentRenderFrameInfo; }); diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index d57dddf512..3a5f027013 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include "OpenVrHelpers.h" @@ -198,6 +199,7 @@ bool OpenVrDisplayPlugin::beginFrameRender(uint32_t frameIndex) { } withRenderThreadLock([&] { + _uiModelTransform = DependencyManager::get()->getModelTransform(); // Make controller poses available to the presentation thread _handPoses = handPoses; _frameInfos[frameIndex] = _currentRenderFrameInfo; From 5759c2d29d1c058b8190ee0fff6b5662939fbc73 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Tue, 14 Jun 2016 11:28:44 -0700 Subject: [PATCH 51/93] final --- scripts/system/controllers/handControllerPointer.js | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/scripts/system/controllers/handControllerPointer.js b/scripts/system/controllers/handControllerPointer.js index f5c0c4bb7c..7046ed16a5 100644 --- a/scripts/system/controllers/handControllerPointer.js +++ b/scripts/system/controllers/handControllerPointer.js @@ -364,13 +364,12 @@ Script.scriptEnding.connect(function () { overlays.forEach(Overlays.deleteOverlay); }); var visualizationIsShowing = false; // Not whether it desired, but simply whether it is. Just an optimziation. -var SYSTEM_LASER_DIRECTION = Vec3.normalize({x: 0, y: -1, z: -1}); // Guessing 45 degrees. +var SYSTEM_LASER_DIRECTION = {x: 0, y: 0, z: -1}; var systemLaserOn = false; function clearSystemLaser() { if (!systemLaserOn) { return; } - print('FIXME remove: disableHandLasers', BOTH_HUD_LASERS); HMD.disableHandLasers(BOTH_HUD_LASERS); systemLaserOn = false; } @@ -379,13 +378,9 @@ function turnOffVisualization(optionalEnableClicks) { // because we're showing c expireMouseCursor(); clearSystemLaser(); } else if (!systemLaserOn) { - print('FIXME remove: setHandLasers', activeHudLaser, true, JSON.stringify(LASER_COLOR_XYZW), JSON.stringify(SYSTEM_LASER_DIRECTION)); // If the active plugin doesn't implement hand lasers, show the mouse reticle instead. - /*Reticle.visible = !*/HMD.setHandLasers(activeHudLaser, true, LASER_COLOR_XYZW, SYSTEM_LASER_DIRECTION); - Reticle.visible = true; // FIXME: just for now, while hand lasers has the bug that requires this. - systemLaserOn = true; - } else { - Reticle.visible = true; + systemLaserOn = HMD.setHandLasers(activeHudLaser, true, LASER_COLOR_XYZW, SYSTEM_LASER_DIRECTION); + Reticle.visible = !systemLaserOn; } if (!visualizationIsShowing) { return; From 67aac09033d0e713adb1e41b4d5bd8dca2c607b2 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 14 Jun 2016 14:59:29 -0700 Subject: [PATCH 52/93] Set state debug flag to false --- scripts/system/controllers/handControllerGrab.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 6daafa425f..6e39b16f82 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -19,7 +19,7 @@ Script.include("/~/system/libraries/utils.js"); // add lines where the hand ray picking is happening // var WANT_DEBUG = false; -var WANT_DEBUG_STATE = true; +var WANT_DEBUG_STATE = false; var WANT_DEBUG_SEARCH_NAME = null; // From ae65790bf3f0341319e25217cd61d4d802ebc579 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Tue, 14 Jun 2016 18:07:08 -0700 Subject: [PATCH 53/93] no laser (or mouse cursor) from hand controllers unless trigger squeezed --- scripts/system/controllers/handControllerPointer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/controllers/handControllerPointer.js b/scripts/system/controllers/handControllerPointer.js index 7046ed16a5..7371680c32 100644 --- a/scripts/system/controllers/handControllerPointer.js +++ b/scripts/system/controllers/handControllerPointer.js @@ -377,7 +377,7 @@ function turnOffVisualization(optionalEnableClicks) { // because we're showing c if (!optionalEnableClicks) { expireMouseCursor(); clearSystemLaser(); - } else if (!systemLaserOn) { + } else if (!systemLaserOn && activeTrigger.state) { // If the active plugin doesn't implement hand lasers, show the mouse reticle instead. systemLaserOn = HMD.setHandLasers(activeHudLaser, true, LASER_COLOR_XYZW, SYSTEM_LASER_DIRECTION); Reticle.visible = !systemLaserOn; From b9754c85649a80d1332a573f120f8f11efc3bf13 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Tue, 14 Jun 2016 20:17:52 -0700 Subject: [PATCH 54/93] continue grabbing through overlays --- scripts/system/controllers/handControllerGrab.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 53c16f26c1..25889c9c1c 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -361,11 +361,15 @@ function MyController(hand) { var _this = this; + this.ignoreInput = function () { + return (_this.state <= STATE_HOLD_SEARCHING) && isIn2DMode(); + }; + this.update = function() { this.updateSmoothedTrigger(); - if (isIn2DMode()) { + if (_this.ignoreInput()) { _this.turnOffVisualizations(); return; } From 0b0977f362e6e114e37e7e4d20f5701a47f3c2a5 Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Tue, 14 Jun 2016 18:25:01 -0700 Subject: [PATCH 55/93] Support keyboard suppresion, control suppresion while in keyboard mode --- interface/resources/controllers/vive.json | 15 ++- .../src/scripting/HMDScriptingInterface.cpp | 12 ++ .../src/scripting/HMDScriptingInterface.h | 17 ++- libraries/plugins/src/plugins/DisplayPlugin.h | 126 ++++++++++-------- libraries/shared/src/shared/Bilateral.h | 49 +++++++ plugins/oculus/src/OculusHelpers.cpp | 8 ++ plugins/openvr/src/OpenVrDisplayPlugin.cpp | 24 ++++ plugins/openvr/src/OpenVrDisplayPlugin.h | 6 + plugins/openvr/src/ViveControllerManager.cpp | 6 + 9 files changed, 206 insertions(+), 57 deletions(-) create mode 100644 libraries/shared/src/shared/Bilateral.h diff --git a/interface/resources/controllers/vive.json b/interface/resources/controllers/vive.json index 95c8f5a734..677ec1ccfc 100644 --- a/interface/resources/controllers/vive.json +++ b/interface/resources/controllers/vive.json @@ -4,15 +4,24 @@ { "from": "Vive.LY", "when": "Vive.LSY", "filters": ["invert"], "to": "Standard.LY" }, { "from": "Vive.LX", "when": "Vive.LSX", "to": "Standard.LX" }, - { "from": "Vive.LT", "to": "Standard.LT" }, + { + "from": "Vive.LT", "to": "Standard.LT", + "filters": [ + { "type": "deadZone", "min": 0.05 }, + ] + }, { "from": "Vive.LeftGrip", "to": "Standard.LeftGrip" }, { "from": "Vive.LS", "to": "Standard.LS" }, { "from": "Vive.LSTouch", "to": "Standard.LSTouch" }, { "from": "Vive.RY", "when": "Vive.RSY", "filters": ["invert"], "to": "Standard.RY" }, { "from": "Vive.RX", "when": "Vive.RSX", "to": "Standard.RX" }, - - { "from": "Vive.RT", "to": "Standard.RT" }, + { + "from": "Vive.RT", "to": "Standard.RT", + "filters": [ + { "type": "deadZone", "min": 0.05 }, + ] + }, { "from": "Vive.RightGrip", "to": "Standard.RightGrip" }, { "from": "Vive.RS", "to": "Standard.RS" }, { "from": "Vive.RSTouch", "to": "Standard.RSTouch" }, diff --git a/interface/src/scripting/HMDScriptingInterface.cpp b/interface/src/scripting/HMDScriptingInterface.cpp index 02840a9775..4dd43dfdf1 100644 --- a/interface/src/scripting/HMDScriptingInterface.cpp +++ b/interface/src/scripting/HMDScriptingInterface.cpp @@ -115,3 +115,15 @@ bool HMDScriptingInterface::setHandLasers(int hands, bool enabled, const glm::ve void HMDScriptingInterface::disableHandLasers(int hands) const { qApp->getActiveDisplayPlugin()->setHandLaser(hands, DisplayPlugin::HandLaserMode::None); } + +bool HMDScriptingInterface::suppressKeyboard() { + return qApp->getActiveDisplayPlugin()->suppressKeyboard(); +} + +void HMDScriptingInterface::unsuppressKeyboard() { + qApp->getActiveDisplayPlugin()->unsuppressKeyboard(); +} + +bool HMDScriptingInterface::isKeyboardVisible() { + return qApp->getActiveDisplayPlugin()->isKeyboardVisible(); +} diff --git a/interface/src/scripting/HMDScriptingInterface.h b/interface/src/scripting/HMDScriptingInterface.h index c55320ca83..e2985b1ce5 100644 --- a/interface/src/scripting/HMDScriptingInterface.h +++ b/interface/src/scripting/HMDScriptingInterface.h @@ -1,4 +1,4 @@ -// + // HMDScriptingInterface.h // interface/src/scripting // @@ -12,6 +12,8 @@ #ifndef hifi_HMDScriptingInterface_h #define hifi_HMDScriptingInterface_h +#include + #include class QScriptContext; class QScriptEngine; @@ -38,6 +40,19 @@ public: Q_INVOKABLE QString preferredAudioOutput() const; Q_INVOKABLE bool setHandLasers(int hands, bool enabled, const glm::vec4& color, const glm::vec3& direction) const; Q_INVOKABLE void disableHandLasers(int hands) const; + /// Suppress the activation of any on-screen keyboard so that a script operation will + /// not be interrupted by a keyboard popup + /// Returns false if there is already an active keyboard displayed. + /// Clients should re-enable the keyboard when the operation is complete and ensure + /// that they balance any call to suppressKeyboard() that returns true with a corresponding + /// call to unsuppressKeyboard() within a reasonable amount of time + Q_INVOKABLE bool suppressKeyboard(); + + /// Enable the keyboard following a suppressKeyboard call + Q_INVOKABLE void unsuppressKeyboard(); + + /// Query the display plugin to determine the current VR keyboard visibility + Q_INVOKABLE bool isKeyboardVisible(); public: HMDScriptingInterface(); diff --git a/libraries/plugins/src/plugins/DisplayPlugin.h b/libraries/plugins/src/plugins/DisplayPlugin.h index 9cb4e071f3..5f659f45f9 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.h +++ b/libraries/plugins/src/plugins/DisplayPlugin.h @@ -16,16 +16,18 @@ #include #include #include -class QImage; #include #include +#include #include "Plugin.h" +class QImage; + enum Eye { - Left, - Right + Left = bilateral::Side::Left, + Right = bilateral::Side::Right }; /* @@ -56,7 +58,73 @@ namespace gpu { using TexturePointer = std::shared_ptr; } -class DisplayPlugin : public Plugin { +// Stereo display functionality +// TODO move out of this file don't derive DisplayPlugin from this. Instead use dynamic casting when +// displayPlugin->isStereo returns true +class StereoDisplay { +public: + // Stereo specific methods + virtual glm::mat4 getEyeProjection(Eye eye, const glm::mat4& baseProjection) const { + return baseProjection; + } + + virtual glm::mat4 getCullingProjection(const glm::mat4& baseProjection) const { + return baseProjection; + } + + virtual float getIPD() const { return AVERAGE_HUMAN_IPD; } +}; + +// HMD display functionality +// TODO move out of this file don't derive DisplayPlugin from this. Instead use dynamic casting when +// displayPlugin->isHmd returns true +class HmdDisplay : public StereoDisplay { +public: + // HMD specific methods + // TODO move these into another class? + virtual glm::mat4 getEyeToHeadTransform(Eye eye) const { + static const glm::mat4 transform; return transform; + } + + // returns a copy of the most recent head pose, computed via updateHeadPose + virtual glm::mat4 getHeadPose() const { + return glm::mat4(); + } + + // Needed for timewarp style features + virtual void setEyeRenderPose(uint32_t frameIndex, Eye eye, const glm::mat4& pose) { + // NOOP + } + + virtual void abandonCalibration() {} + + virtual void resetSensors() {} + + enum Hand { + LeftHand = 0x01, + RightHand = 0x02, + }; + + enum class HandLaserMode { + None, // Render no hand lasers + Overlay, // Render hand lasers only if they intersect with the UI layer, and stop at the UI layer + }; + + virtual bool setHandLaser( + uint32_t hands, // Bits from the Hand enum + HandLaserMode mode, // Mode in which to render + const vec4& color = vec4(1), // The color of the rendered laser + const vec3& direction = vec3(0, 0, -1) // The direction in which to render the hand lasers + ) { + return false; + } + + virtual bool suppressKeyboard() { return false; } + virtual void unsuppressKeyboard() {}; + virtual bool isKeyboardVisible() { return false; } +}; + +class DisplayPlugin : public Plugin, public HmdDisplay { Q_OBJECT using Parent = Plugin; public: @@ -117,42 +185,12 @@ public: return QRect(0, 0, recommendedSize.x, recommendedSize.y); } - // Stereo specific methods - virtual glm::mat4 getEyeProjection(Eye eye, const glm::mat4& baseProjection) const { - return baseProjection; - } - - virtual glm::mat4 getCullingProjection(const glm::mat4& baseProjection) const { - return baseProjection; - } - - // Fetch the most recently displayed image as a QImage virtual QImage getScreenshot() const = 0; - // HMD specific methods - // TODO move these into another class? - virtual glm::mat4 getEyeToHeadTransform(Eye eye) const { - static const glm::mat4 transform; return transform; - } - // will query the underlying hmd api to compute the most recent head pose virtual bool beginFrameRender(uint32_t frameIndex) { return true; } - // returns a copy of the most recent head pose, computed via updateHeadPose - virtual glm::mat4 getHeadPose() const { - return glm::mat4(); - } - - // Needed for timewarp style features - virtual void setEyeRenderPose(uint32_t frameIndex, Eye eye, const glm::mat4& pose) { - // NOOP - } - - virtual float getIPD() const { return AVERAGE_HUMAN_IPD; } - - virtual void abandonCalibration() {} - virtual void resetSensors() {} virtual float devicePixelRatio() { return 1.0f; } // Rate at which we present to the display device virtual float presentRate() const { return -1.0f; } @@ -160,6 +198,7 @@ public: virtual float newFramePresentRate() const { return -1.0f; } // Rate at which rendered frames are being skipped virtual float droppedFrameRate() const { return -1.0f; } + uint32_t presentCount() const { return _presentedFrameIndex; } // Time since last call to incrementPresentCount (only valid if DEBUG_PAINT_DELAY is defined) int64_t getPaintDelayUsecs() const; @@ -168,25 +207,6 @@ public: static const QString& MENU_PATH(); - enum Hand { - LeftHand = 0x01, - RightHand = 0x02, - }; - - enum class HandLaserMode { - None, // Render no hand lasers - Overlay, // Render hand lasers only if they intersect with the UI layer, and stop at the UI layer - }; - - virtual bool setHandLaser( - uint32_t hands, // Bits from the Hand enum - HandLaserMode mode, // Mode in which to render - const vec4& color = vec4(1), // The color of the rendered laser - const vec3& direction = vec3(0, 0, -1) // The direction in which to render the hand lasers - ) { - return false; - } - signals: void recommendedFramebufferSizeChanged(const QSize & size); diff --git a/libraries/shared/src/shared/Bilateral.h b/libraries/shared/src/shared/Bilateral.h new file mode 100644 index 0000000000..c4daf60177 --- /dev/null +++ b/libraries/shared/src/shared/Bilateral.h @@ -0,0 +1,49 @@ +// +// Created by Bradley Austin Davis 2015/10/09 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#pragma once + +namespace bilateral { + enum class Side { + Left = 0, + Right = 1 + }; + + using Indices = Side; + + enum class Bits { + Left = 0x01, + Right = 0x02 + }; + + inline uint8_t bit(Side side) { + switch (side) { + case Side::Left: + return 0x01; + case Side::Right: + return 0x02; + } + return UINT8_MAX; + } + + inline uint8_t index(Side side) { + switch (side) { + case Side::Left: + return 0; + case Side::Right: + return 1; + } + return UINT8_MAX; + } + + template + void for_each_side(F f) { + f(Side::Left); + f(Side::Right); + } +} diff --git a/plugins/oculus/src/OculusHelpers.cpp b/plugins/oculus/src/OculusHelpers.cpp index 705c0a0781..49c14c8d66 100644 --- a/plugins/oculus/src/OculusHelpers.cpp +++ b/plugins/oculus/src/OculusHelpers.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -53,6 +54,13 @@ bool oculusAvailable() { static std::once_flag once; static bool result { false }; std::call_once(once, [&] { + + static const QString DEBUG_FLAG("HIFI_DEBUG_OPENVR"); + static bool enableDebugOpenVR = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG); + if (enableDebugOpenVR) { + return; + } + ovrDetectResult detect = ovr_Detect(0); if (!detect.IsOculusServiceRunning || !detect.IsOculusHMDConnected) { return; diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index 3a5f027013..acbf386980 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -253,3 +253,27 @@ void OpenVrDisplayPlugin::updatePresentPose() { mat3 presentRotation(_currentPresentFrameInfo.rawPresentPose); _currentPresentFrameInfo.presentReprojection = glm::mat3(glm::inverse(renderRotation) * presentRotation); } + +bool OpenVrDisplayPlugin::suppressKeyboard() { + if (isOpenVrKeyboardShown()) { + return false; + } + if (!_keyboardSupressionCount.fetch_add(1)) { + disableOpenVrKeyboard(); + } + return true; +} + +void OpenVrDisplayPlugin::unsuppressKeyboard() { + if (_keyboardSupressionCount == 0) { + qWarning() << "Attempted to unsuppress a keyboard that was not suppressed"; + return; + } + if (1 == _keyboardSupressionCount.fetch_sub(1)) { + enableOpenVrKeyboard(); + } +} + +bool OpenVrDisplayPlugin::isKeyboardVisible() { + return isOpenVrKeyboardShown(); +} diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.h b/plugins/openvr/src/OpenVrDisplayPlugin.h index ee693b8091..b22fadb52c 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.h +++ b/plugins/openvr/src/OpenVrDisplayPlugin.h @@ -30,6 +30,10 @@ public: bool beginFrameRender(uint32_t frameIndex) override; void cycleDebugOutput() override { _lockCurrentTexture = !_lockCurrentTexture; } + bool suppressKeyboard() override; + void unsuppressKeyboard() override; + bool isKeyboardVisible() override; + protected: bool internalActivate() override; void internalDeactivate() override; @@ -39,8 +43,10 @@ protected: bool isHmdMounted() const override; void postPreview() override; + private: vr::IVRSystem* _system { nullptr }; std::atomic _hmdActivityLevel { vr::k_EDeviceActivityLevel_Unknown }; + std::atomic _keyboardSupressionCount{ 0 }; static const QString NAME; }; diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 69c98d48c3..631efca4a2 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -240,6 +240,12 @@ void ViveControllerManager::InputDevice::update(float deltaTime, const controlle _poseStateMap.clear(); _buttonPressedMap.clear(); + // While the keyboard is open, we defer strictly to the keyboard values + if (isOpenVrKeyboardShown()) { + _axisStateMap.clear(); + return; + } + PerformanceTimer perfTimer("ViveControllerManager::update"); auto leftHandDeviceIndex = _system->GetTrackedDeviceIndexForControllerRole(vr::TrackedControllerRole_LeftHand); From 60035486d3e2835708756e7bdcc5165138f34cff Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Wed, 15 Jun 2016 09:35:24 -0700 Subject: [PATCH 56/93] Build fix --- libraries/plugins/src/plugins/DisplayPlugin.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/plugins/src/plugins/DisplayPlugin.h b/libraries/plugins/src/plugins/DisplayPlugin.h index 5f659f45f9..a65e8e7371 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.h +++ b/libraries/plugins/src/plugins/DisplayPlugin.h @@ -26,8 +26,8 @@ class QImage; enum Eye { - Left = bilateral::Side::Left, - Right = bilateral::Side::Right + Left = (int)bilateral::Side::Left, + Right = (int)bilateral::Side::Right }; /* From 6366ca7508ce77de4230900141298669f3c49fcf Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 15 Jun 2016 11:08:59 -0700 Subject: [PATCH 57/93] Go red for full trigger. --- .../system/controllers/handControllerPointer.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/scripts/system/controllers/handControllerPointer.js b/scripts/system/controllers/handControllerPointer.js index 7371680c32..5873040dd9 100644 --- a/scripts/system/controllers/handControllerPointer.js +++ b/scripts/system/controllers/handControllerPointer.js @@ -349,7 +349,8 @@ clickMapping.enable(); // Same properties as handControllerGrab search sphere var BALL_SIZE = 0.011; var BALL_ALPHA = 0.5; -var LASER_COLOR_XYZW = {x: 10 / 255, y: 10 / 255, z: 255 / 255, w: BALL_ALPHA}; +var LASER_SEARCH_COLOR_XYZW = {x: 10 / 255, y: 10 / 255, z: 255 / 255, w: BALL_ALPHA}; +var LASER_TRIGGER_COLOR_XYZW = {x: 250 / 255, y: 10 / 255, z: 10 / 255, w: BALL_ALPHA}; var fakeProjectionBall = Overlays.addOverlay("sphere", { size: 5 * BALL_SIZE, color: {red: 255, green: 10, blue: 10}, @@ -373,15 +374,24 @@ function clearSystemLaser() { HMD.disableHandLasers(BOTH_HUD_LASERS); systemLaserOn = false; } +function setColoredLaser() { // answer trigger state if lasers supported, else falsey. + var color = (activeTrigger.state === 'full') ? LASER_TRIGGER_COLOR_XYZW : LASER_SEARCH_COLOR_XYZW; + return HMD.setHandLasers(activeHudLaser, true, color, SYSTEM_LASER_DIRECTION) && activeTrigger.state; + +} function turnOffVisualization(optionalEnableClicks) { // because we're showing cursor on HUD if (!optionalEnableClicks) { expireMouseCursor(); clearSystemLaser(); - } else if (!systemLaserOn && activeTrigger.state) { + } else if (activeTrigger.state && (!systemLaserOn || (systemLaserOn !== activeTrigger.state))) { // last=>wrong color // If the active plugin doesn't implement hand lasers, show the mouse reticle instead. - systemLaserOn = HMD.setHandLasers(activeHudLaser, true, LASER_COLOR_XYZW, SYSTEM_LASER_DIRECTION); + systemLaserOn = setColoredLaser(); Reticle.visible = !systemLaserOn; + } else if ((systemLaserOn || Reticle.visible) && !activeTrigger.state) { + clearSystemLaser(); + Reticle.visible = false; } + if (!visualizationIsShowing) { return; } From bc838ac0b476f586723ecf81a8e8cb86d647e8d6 Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Wed, 15 Jun 2016 11:41:28 -0700 Subject: [PATCH 58/93] Fix vive json --- interface/resources/controllers/vive.json | 5 ++--- interface/src/scripting/HMDScriptingInterface.h | 3 ++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/interface/resources/controllers/vive.json b/interface/resources/controllers/vive.json index 677ec1ccfc..6be672900a 100644 --- a/interface/resources/controllers/vive.json +++ b/interface/resources/controllers/vive.json @@ -3,11 +3,10 @@ "channels": [ { "from": "Vive.LY", "when": "Vive.LSY", "filters": ["invert"], "to": "Standard.LY" }, { "from": "Vive.LX", "when": "Vive.LSX", "to": "Standard.LX" }, - { "from": "Vive.LT", "to": "Standard.LT", "filters": [ - { "type": "deadZone", "min": 0.05 }, + { "type": "deadZone", "min": 0.05 } ] }, { "from": "Vive.LeftGrip", "to": "Standard.LeftGrip" }, @@ -19,7 +18,7 @@ { "from": "Vive.RT", "to": "Standard.RT", "filters": [ - { "type": "deadZone", "min": 0.05 }, + { "type": "deadZone", "min": 0.05 } ] }, { "from": "Vive.RightGrip", "to": "Standard.RightGrip" }, diff --git a/interface/src/scripting/HMDScriptingInterface.h b/interface/src/scripting/HMDScriptingInterface.h index e2985b1ce5..2739522adf 100644 --- a/interface/src/scripting/HMDScriptingInterface.h +++ b/interface/src/scripting/HMDScriptingInterface.h @@ -33,12 +33,13 @@ public: Q_INVOKABLE glm::vec3 calculateRayUICollisionPoint(const glm::vec3& position, const glm::vec3& direction) const; Q_INVOKABLE glm::vec2 overlayFromWorldPoint(const glm::vec3& position) const; Q_INVOKABLE glm::vec3 worldPointFromOverlay(const glm::vec2& overlay) const; - Q_INVOKABLE glm::vec2 sphericalToOverlay(const glm::vec2 & sphericalPos) const; Q_INVOKABLE glm::vec2 overlayToSpherical(const glm::vec2 & overlayPos) const; Q_INVOKABLE QString preferredAudioInput() const; Q_INVOKABLE QString preferredAudioOutput() const; + Q_INVOKABLE bool setHandLasers(int hands, bool enabled, const glm::vec4& color, const glm::vec3& direction) const; + Q_INVOKABLE void disableHandLasers(int hands) const; /// Suppress the activation of any on-screen keyboard so that a script operation will /// not be interrupted by a keyboard popup From 2c7642a3674aa100e46c2dd4a6ba2dc6f6ba5e80 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 15 Jun 2016 12:28:42 -0700 Subject: [PATCH 59/93] Do not make ReticleClick when we enter an overlay with the trigger pressed. --- .../controllers/handControllerPointer.js | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/scripts/system/controllers/handControllerPointer.js b/scripts/system/controllers/handControllerPointer.js index 7046ed16a5..2faf2cb361 100644 --- a/scripts/system/controllers/handControllerPointer.js +++ b/scripts/system/controllers/handControllerPointer.js @@ -48,9 +48,10 @@ function TimeLock(expiration) { } var handControllerLockOut = new TimeLock(2000); -function Trigger() { +function Trigger(label) { // This part is copied and adapted from handControllerGrab.js. Maybe we should refactor this. var that = this; + that.label = label; that.TRIGGER_SMOOTH_RATIO = 0.1; // Time averaging of trigger - 0.0 disables smoothing that.TRIGGER_ON_VALUE = 0.4; // Squeezed just enough to activate search or near grab that.TRIGGER_GRAB_VALUE = 0.85; // Squeezed far enough to complete distant grab @@ -301,8 +302,8 @@ setupHandler(Controller.mouseDoublePressEvent, onMouseClick); // CONTROLLER MAPPING --------- // -var leftTrigger = new Trigger(); -var rightTrigger = new Trigger(); +var leftTrigger = new Trigger('left'); +var rightTrigger = new Trigger('right'); var activeTrigger = rightTrigger; var activeHand = Controller.Standard.RightHand; var LEFT_HUD_LASER = 1; @@ -336,8 +337,26 @@ Script.scriptEnding.connect(clickMapping.disable); clickMapping.from(Controller.Standard.RT).peek().to(rightTrigger.triggerPress); clickMapping.from(Controller.Standard.LT).peek().to(leftTrigger.triggerPress); // Full smoothed trigger is a click. -clickMapping.from(rightTrigger.full).when(isPointingAtOverlay).to(Controller.Actions.ReticleClick); -clickMapping.from(leftTrigger.full).when(isPointingAtOverlay).to(Controller.Actions.ReticleClick); +function isPointingAtOverlayStartedNonFullTrigger(trigger) { + // true if isPointingAtOverlay AND we were NOT full triggered when we became so. + // The idea is to not count clicks when we're full-triggering and reach the edge of a window. + var lockedIn = false; + return function () { + if (trigger !== activeTrigger) { + return lockedIn = false; + } + if (!isPointingAtOverlay()) { + return lockedIn = false; + } + if (lockedIn) { + return true; + } + lockedIn = !trigger.full(); + return lockedIn; + } +} +clickMapping.from(rightTrigger.full).when(isPointingAtOverlayStartedNonFullTrigger(rightTrigger)).to(Controller.Actions.ReticleClick); +clickMapping.from(leftTrigger.full).when(isPointingAtOverlayStartedNonFullTrigger(leftTrigger)).to(Controller.Actions.ReticleClick); clickMapping.from(Controller.Standard.RightSecondaryThumb).peek().to(Controller.Actions.ContextMenu); clickMapping.from(Controller.Standard.LeftSecondaryThumb).peek().to(Controller.Actions.ContextMenu); // Partial smoothed trigger is activation. From 1e3b20b66d9445f49368573902f29fd26cc5df8b Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 15 Jun 2016 15:06:36 -0700 Subject: [PATCH 60/93] Explicit check enumerated states instead of relying on order, per feedback. --- scripts/system/controllers/handControllerGrab.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 25889c9c1c..ea2c4c1653 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -361,8 +361,9 @@ function MyController(hand) { var _this = this; + var suppressedIn2D = [STATE_OFF, STATE_SEARCHING, STATE_HOLD_SEARCHING]; this.ignoreInput = function () { - return (_this.state <= STATE_HOLD_SEARCHING) && isIn2DMode(); + return (-1 !== suppressedIn2D.indexOf(_this.state)) && isIn2DMode(); }; this.update = function() { From 87dc6649ceacffb819516125af4ee3d284d6a997 Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Wed, 15 Jun 2016 15:16:29 -0700 Subject: [PATCH 61/93] Better track keyboard open state, fix text replacement --- plugins/openvr/src/OpenVrHelpers.cpp | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/plugins/openvr/src/OpenVrHelpers.cpp b/plugins/openvr/src/OpenVrHelpers.cpp index 4b4eef0538..4a756e12b0 100644 --- a/plugins/openvr/src/OpenVrHelpers.cpp +++ b/plugins/openvr/src/OpenVrHelpers.cpp @@ -164,12 +164,13 @@ void finishOpenVrKeyboardInput() { auto offscreenUi = DependencyManager::get(); auto chars = _overlay->GetKeyboardText(textArray, 8192); auto newText = QString(QByteArray(textArray, chars)); - // TODO modify the new text to match the possible input hints: - // ImhDigitsOnly ImhFormattedNumbersOnly ImhUppercaseOnly ImhLowercaseOnly - // ImhDialableCharactersOnly ImhEmailCharactersOnly ImhUrlCharactersOnly ImhLatinOnly - QInputMethodEvent event(_existingText, QList()); - event.setCommitString(newText, 0, _existingText.size()); - qApp->sendEvent(_keyboardFocusObject, &event); + _keyboardFocusObject->setProperty("text", newText); + //// TODO modify the new text to match the possible input hints: + //// ImhDigitsOnly ImhFormattedNumbersOnly ImhUppercaseOnly ImhLowercaseOnly + //// ImhDialableCharactersOnly ImhEmailCharactersOnly ImhUrlCharactersOnly ImhLatinOnly + //QInputMethodEvent event(_existingText, QList()); + //event.setCommitString(newText, 0, _existingText.size()); + //qApp->sendEvent(_keyboardFocusObject, &event); // Simulate an enter press on the top level window to trigger the action if (0 == (_currentHints & Qt::ImhMultiLine)) { qApp->sendEvent(offscreenUi->getWindow(), &QKeyEvent(QEvent::KeyPress, Qt::Key_Return, Qt::KeyboardModifiers(), QString("\n"))); @@ -233,6 +234,11 @@ void handleOpenVrEvents() { case vr::VREvent_KeyboardDone: finishOpenVrKeyboardInput(); + + // FALL THROUGH + case vr::VREvent_KeyboardClosed: + _keyboardFocusObject = nullptr; + _keyboardShown = false; break; default: From 6f671b7cde167005965a47539e048b09bf8f5171 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 15 Jun 2016 15:17:34 -0700 Subject: [PATCH 62/93] see comment. --- scripts/system/controllers/handControllerGrab.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index ea2c4c1653..316fe0ff88 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -363,15 +363,17 @@ function MyController(hand) { var suppressedIn2D = [STATE_OFF, STATE_SEARCHING, STATE_HOLD_SEARCHING]; this.ignoreInput = function () { - return (-1 !== suppressedIn2D.indexOf(_this.state)) && isIn2DMode(); + // We've made the decision to use 'this' for new code, even though it is fragile, + // in order to keep/ the code uniform without making any no-op line changes. + return (-1 !== suppressedIn2D.indexOf(this.state)) && isIn2DMode(); }; this.update = function() { this.updateSmoothedTrigger(); - if (_this.ignoreInput()) { - _this.turnOffVisualizations(); + if (this.ignoreInput()) { + this.turnOffVisualizations(); return; } From 6ad1008a566f179e7752af7456c5d3a4a3e55efa Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Wed, 15 Jun 2016 13:42:48 -0700 Subject: [PATCH 63/93] Allow explicit overlay alpha from scripts --- .../scripting/DesktopScriptingInterface.cpp | 6 +++++ .../src/scripting/DesktopScriptingInterface.h | 2 ++ .../src/display-plugins/CompositorHelper.h | 6 +++++ .../display-plugins/OpenGLDisplayPlugin.cpp | 27 +++++++++++++++++++ .../src/display-plugins/OpenGLDisplayPlugin.h | 8 +++++- .../display-plugins/hmd/HmdDisplayPlugin.cpp | 9 ++++++- .../oculus/src/OculusBaseDisplayPlugin.cpp | 2 +- .../src/OculusLegacyDisplayPlugin.cpp | 3 ++- plugins/openvr/src/OpenVrDisplayPlugin.cpp | 2 +- 9 files changed, 60 insertions(+), 5 deletions(-) diff --git a/interface/src/scripting/DesktopScriptingInterface.cpp b/interface/src/scripting/DesktopScriptingInterface.cpp index 843a40348e..f7bc8afe36 100644 --- a/interface/src/scripting/DesktopScriptingInterface.cpp +++ b/interface/src/scripting/DesktopScriptingInterface.cpp @@ -16,6 +16,7 @@ #include "Application.h" #include "MainWindow.h" +#include int DesktopScriptingInterface::getWidth() { QSize size = qApp->getWindow()->windowHandle()->screen()->virtualSize(); @@ -25,3 +26,8 @@ int DesktopScriptingInterface::getHeight() { QSize size = qApp->getWindow()->windowHandle()->screen()->virtualSize(); return size.height(); } + +void DesktopScriptingInterface::setOverlayAlpha(float alpha) { + qApp->getApplicationCompositor().setAlpha(alpha); +} + diff --git a/interface/src/scripting/DesktopScriptingInterface.h b/interface/src/scripting/DesktopScriptingInterface.h index be4eaadbfb..8da502cb11 100644 --- a/interface/src/scripting/DesktopScriptingInterface.h +++ b/interface/src/scripting/DesktopScriptingInterface.h @@ -22,6 +22,8 @@ class DesktopScriptingInterface : public QObject, public Dependency { Q_PROPERTY(int height READ getHeight) // Physical height of screen(s) including task bars and system menus public: + Q_INVOKABLE void setOverlayAlpha(float alpha); + int getWidth(); int getHeight(); }; diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.h b/libraries/display-plugins/src/display-plugins/CompositorHelper.h index 868beec53f..2a3dd0c852 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.h +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.h @@ -38,6 +38,7 @@ const float MAGNIFY_MULT = 2.0f; class CompositorHelper : public QObject, public Dependency { Q_OBJECT + Q_PROPERTY(float alpha READ getAlpha WRITE setAlpha NOTIFY alphaChanged) Q_PROPERTY(bool reticleOverDesktop READ getReticleOverDesktop WRITE setReticleOverDesktop) public: static const uvec2 VIRTUAL_SCREEN_SIZE; @@ -74,6 +75,9 @@ public: void setModelTransform(const Transform& transform) { _modelTransform = transform; } const Transform& getModelTransform() const { return _modelTransform; } + float getAlpha() const { return _alpha; } + void setAlpha(float alpha) { if (alpha != _alpha) { emit alphaChanged(); _alpha = alpha; } } + bool getReticleVisible() const { return _reticleVisible; } void setReticleVisible(bool visible) { _reticleVisible = visible; } @@ -109,6 +113,7 @@ public: signals: void allowMouseCaptureChanged(); + void alphaChanged(); protected slots: void sendFakeMouseEvent(); @@ -134,6 +139,7 @@ private: float _textureFov { VIRTUAL_UI_TARGET_FOV.y }; float _textureAspectRatio { VIRTUAL_UI_ASPECT_RATIO }; + float _alpha { 1.0f }; float _hmdUIRadius { 1.0f }; int _previousBorderWidth { -1 }; diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 23c836b3de..b49b41d8b2 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -273,11 +273,23 @@ bool OpenGLDisplayPlugin::activate() { _container->makeRenderingContextCurrent(); #endif + auto compositorHelper = DependencyManager::get(); + connect(compositorHelper.data(), &CompositorHelper::alphaChanged, [this] { + auto compositorHelper = DependencyManager::get(); + auto animation = new QPropertyAnimation(this, "overlayAlpha"); + animation->setDuration(200); + animation->setEndValue(compositorHelper->getAlpha()); + animation->start(); + }); + return DisplayPlugin::activate(); } void OpenGLDisplayPlugin::deactivate() { + auto compositorHelper = DependencyManager::get(); + disconnect(compositorHelper.data()); + #if THREADED_PRESENT auto presentThread = DependencyManager::get(); // Does not return until the GL transition has completeed @@ -445,6 +457,8 @@ void OpenGLDisplayPlugin::compositeOverlay() { auto compositorHelper = DependencyManager::get(); useProgram(_program); + // set the alpha + Uniform(*_program, _alphaUniform).Set(_compositeOverlayAlpha); // check the alpha // Overlay draw if (isStereo()) { @@ -458,6 +472,8 @@ void OpenGLDisplayPlugin::compositeOverlay() { Uniform(*_program, _mvpUniform).Set(mat4()); drawUnitQuad(); } + // restore the alpha + Uniform(*_program, _alphaUniform).Set(1.0); } void OpenGLDisplayPlugin::compositePointer() { @@ -465,6 +481,8 @@ void OpenGLDisplayPlugin::compositePointer() { auto compositorHelper = DependencyManager::get(); useProgram(_program); + // set the alpha + Uniform(*_program, _alphaUniform).Set(_compositeOverlayAlpha); Uniform(*_program, _mvpUniform).Set(compositorHelper->getReticleTransform(glm::mat4())); if (isStereo()) { for_each_eye([&](Eye eye) { @@ -475,6 +493,8 @@ void OpenGLDisplayPlugin::compositePointer() { drawUnitQuad(); } Uniform(*_program, _mvpUniform).Set(mat4()); + // restore the alpha + Uniform(*_program, _alphaUniform).Set(1.0); } void OpenGLDisplayPlugin::compositeScene() { @@ -682,3 +702,10 @@ void OpenGLDisplayPlugin::assertIsRenderThread() const { void OpenGLDisplayPlugin::assertIsPresentThread() const { Q_ASSERT(QThread::currentThread() == _presentThread); } + +bool OpenGLDisplayPlugin::beginFrameRender(uint32_t frameIndex) { + withRenderThreadLock([&] { + _compositeOverlayAlpha = _overlayAlpha; + }); + return Parent::beginFrameRender(frameIndex); +} diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h index 69653b8c76..d8d4d8ff8c 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -24,6 +24,9 @@ #define THREADED_PRESENT 1 class OpenGLDisplayPlugin : public DisplayPlugin { + Q_OBJECT + Q_PROPERTY(float overlayAlpha MEMBER _overlayAlpha) + using Parent = DisplayPlugin; protected: using Mutex = std::mutex; using Lock = std::unique_lock; @@ -60,6 +63,7 @@ public: float droppedFrameRate() const override; + bool beginFrameRender(uint32_t frameIndex) override; protected: #if THREADED_PRESENT friend class PresentThread; @@ -115,7 +119,8 @@ protected: RateCounter<> _presentRate; QMap _sceneTextureToFrameIndexMap; uint32_t _currentPresentFrameIndex { 0 }; - + float _compositeOverlayAlpha{ 1.0f }; + gpu::TexturePointer _currentSceneTexture; gpu::TexturePointer _currentOverlayTexture; @@ -157,6 +162,7 @@ private: // be serialized through this mutex mutable Mutex _presentMutex; ProgramPtr _activeProgram; + float _overlayAlpha{ 1.0f }; }; diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index 6801d94f2f..efdb68226b 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -287,6 +287,8 @@ void HmdDisplayPlugin::compositeOverlay() { glm::mat4 modelMat = compositorHelper->getModelTransform().getMatrix(); useProgram(_program); + // set the alpha + Uniform(*_program, _alphaUniform).Set(_compositeOverlayAlpha); _sphereSection->Use(); for_each_eye([&](Eye eye) { eyeViewport(eye); @@ -295,6 +297,8 @@ void HmdDisplayPlugin::compositeOverlay() { Uniform(*_program, _mvpUniform).Set(mvp); _sphereSection->Draw(); }); + // restore the alpha + Uniform(*_program, _alphaUniform).Set(1.0); } void HmdDisplayPlugin::compositePointer() { @@ -302,8 +306,9 @@ void HmdDisplayPlugin::compositePointer() { auto compositorHelper = DependencyManager::get(); - // check the alpha useProgram(_program); + // set the alpha + Uniform(*_program, _alphaUniform).Set(_compositeOverlayAlpha); // Mouse pointer _plane->Use(); @@ -317,6 +322,8 @@ void HmdDisplayPlugin::compositePointer() { Uniform(*_program, _mvpUniform).Set(mvp); _plane->Draw(); }); + // restore the alpha + Uniform(*_program, _alphaUniform).Set(1.0); } diff --git a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp index e188bea52e..e26a48b89c 100644 --- a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp @@ -45,7 +45,7 @@ bool OculusBaseDisplayPlugin::beginFrameRender(uint32_t frameIndex) { _handPoses = handPoses; _frameInfos[frameIndex] = _currentRenderFrameInfo; }); - return true; + return Parent::beginFrameRender(frameIndex); } bool OculusBaseDisplayPlugin::isSupported() const { diff --git a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp index 8d2bc24177..699891deaa 100644 --- a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp +++ b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp @@ -41,6 +41,7 @@ void OculusLegacyDisplayPlugin::resetSensors() { } bool OculusLegacyDisplayPlugin::beginFrameRender(uint32_t frameIndex) { + _currentRenderFrameInfo = FrameInfo(); _currentRenderFrameInfo.predictedDisplayTime = _currentRenderFrameInfo.sensorSampleTime = ovr_GetTimeInSeconds(); _trackingState = ovrHmd_GetTrackingState(_hmd, _currentRenderFrameInfo.predictedDisplayTime); @@ -48,7 +49,7 @@ bool OculusLegacyDisplayPlugin::beginFrameRender(uint32_t frameIndex) { withRenderThreadLock([&]{ _frameInfos[frameIndex] = _currentRenderFrameInfo; }); - return true; + return Parent::beginFrameRender(frameIndex); } bool OculusLegacyDisplayPlugin::isSupported() const { diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index acbf386980..a92c930cbf 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -204,7 +204,7 @@ bool OpenVrDisplayPlugin::beginFrameRender(uint32_t frameIndex) { _handPoses = handPoses; _frameInfos[frameIndex] = _currentRenderFrameInfo; }); - return true; + return Parent::beginFrameRender(frameIndex); } void OpenVrDisplayPlugin::hmdPresent() { From 33e9caa63687f0682d3eff619e963b8952a23b2a Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Wed, 15 Jun 2016 17:20:18 -0700 Subject: [PATCH 64/93] Simplify overlay conductor, menu interation --- .../resources/qml/hifi/ToggleHudButton.qml | 4 +- interface/src/Application.cpp | 13 +- interface/src/ui/OverlayConductor.cpp | 112 +++++------------- interface/src/ui/OverlayConductor.h | 23 +--- 4 files changed, 42 insertions(+), 110 deletions(-) diff --git a/interface/resources/qml/hifi/ToggleHudButton.qml b/interface/resources/qml/hifi/ToggleHudButton.qml index 6454f42305..63c056b352 100644 --- a/interface/resources/qml/hifi/ToggleHudButton.qml +++ b/interface/resources/qml/hifi/ToggleHudButton.qml @@ -16,6 +16,8 @@ Window { height: 50 clip: true visible: true + // Disable this window from being able to call 'desktop.raise() and desktop.showDesktop' + activator: Item {} Item { width: 50 @@ -28,7 +30,7 @@ Window { MouseArea { readonly property string overlayMenuItem: "Overlays" anchors.fill: parent - onClicked: MenuInterface.setIsOptionChecked(overlayMenuItem, !MenuInterface.isOptionChecked(overlayMenuItem)) + onClicked: MenuInterface.setIsOptionChecked(overlayMenuItem, !MenuInterface.isOptionChecked(overlayMenuItem)); } } } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index ec86f5f1e0..29238cf153 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2716,7 +2716,6 @@ void Application::idle(float nsecsElapsed) { if (firstIdle) { firstIdle = false; connect(offscreenUi.data(), &OffscreenUi::showDesktop, this, &Application::showDesktop); - _overlayConductor.setEnabled(Menu::getInstance()->isOptionChecked(MenuOption::Overlays)); } PROFILE_RANGE(__FUNCTION__); @@ -3217,13 +3216,13 @@ void Application::updateThreads(float deltaTime) { } void Application::toggleOverlays() { - auto newOverlaysVisible = !_overlayConductor.getEnabled(); - Menu::getInstance()->setIsOptionChecked(MenuOption::Overlays, newOverlaysVisible); - _overlayConductor.setEnabled(newOverlaysVisible); + auto menu = Menu::getInstance(); + menu->setIsOptionChecked(MenuOption::Overlays, menu->isOptionChecked(MenuOption::Overlays)); } void Application::setOverlaysVisible(bool visible) { - _overlayConductor.setEnabled(visible); + auto menu = Menu::getInstance(); + menu->setIsOptionChecked(MenuOption::Overlays, true); } void Application::cycleCamera() { @@ -5306,9 +5305,7 @@ void Application::readArgumentsFromLocalSocket() const { } void Application::showDesktop() { - if (!_overlayConductor.getEnabled()) { - _overlayConductor.setEnabled(true); - } + Menu::getInstance()->setIsOptionChecked(MenuOption::Overlays, true); } CompositorHelper& Application::getApplicationCompositor() const { diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index a897d85472..a455147bb8 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -99,104 +99,50 @@ void OverlayConductor::centerUI() { qApp->getApplicationCompositor().setModelTransform(Transform(camMat)); } -bool OverlayConductor::userWishesToHide() const { - // user pressed toggle button. - return Menu::getInstance()->isOptionChecked(MenuOption::Overlays) != _prevOverlayMenuChecked && Menu::getInstance()->isOptionChecked(MenuOption::Overlays); -} - -bool OverlayConductor::userWishesToShow() const { - // user pressed toggle button. - return Menu::getInstance()->isOptionChecked(MenuOption::Overlays) != _prevOverlayMenuChecked && !Menu::getInstance()->isOptionChecked(MenuOption::Overlays); -} - -void OverlayConductor::setState(State state) { -#ifdef WANT_DEBUG - static QString stateToString[NumStates] = { "Enabled", "DisabledByDrive", "DisabledByHead", "DisabledByToggle" }; - qDebug() << "OverlayConductor " << stateToString[state] << "<--" << stateToString[_state]; -#endif - _state = state; -} - -OverlayConductor::State OverlayConductor::getState() const { - return _state; -} - void OverlayConductor::update(float dt) { + auto offscreenUi = DependencyManager::get(); + bool currentVisible = !offscreenUi->getDesktop()->property("pinned").toBool(); MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); - - // centerUI when hmd mode is first enabled - if (qApp->isHMDMode() && !_hmdMode) { + // centerUI when hmd mode is first enabled and mounted + if (qApp->isHMDMode() && qApp->getActiveDisplayPlugin()->isDisplayVisible() && !_hmdMode) { + _hmdMode = true; centerUI(); + } else { + _hmdMode = false; } - _hmdMode = qApp->isHMDMode(); bool prevDriving = _currentDriving; bool isDriving = updateAvatarHasDriveInput(); bool drivingChanged = prevDriving != isDriving; - bool isAtRest = updateAvatarIsAtRest(); - switch (getState()) { - case Enabled: - if (myAvatar->getClearOverlayWhenDriving() && qApp->isHMDMode() && headOutsideOverlay()) { - setState(DisabledByHead); - setEnabled(false); - } - if (userWishesToHide()) { - setState(DisabledByToggle); - setEnabled(false); - } - if (myAvatar->getClearOverlayWhenDriving() && drivingChanged && isDriving) { - setState(DisabledByDrive); - setEnabled(false); - } - break; - case DisabledByDrive: - if (!isDriving || userWishesToShow()) { - setState(Enabled); - setEnabled(true); - } - break; - case DisabledByHead: - if (isAtRest || userWishesToShow()) { - setState(Enabled); - setEnabled(true); - } - break; - case DisabledByToggle: - if (userWishesToShow()) { - setState(Enabled); - setEnabled(true); - } - break; - default: - break; + if (_flags & SuppressedByDrive) { + if (!isDriving) { + _flags &= ~SuppressedByDrive; + } + } else { + if (myAvatar->getClearOverlayWhenDriving() && drivingChanged && isDriving) { + _flags |= SuppressedByDrive; + } } - _prevOverlayMenuChecked = Menu::getInstance()->isOptionChecked(MenuOption::Overlays); -} - -void OverlayConductor::setEnabled(bool enabled) { - if (enabled == _enabled) { - return; + if (_flags & SuppressedByHead) { + if (isAtRest) { + _flags &= ~SuppressedByHead; + } + } else { + if (_hmdMode && headOutsideOverlay()) { + _flags |= SuppressedByHead; + } } - _enabled = enabled; // set the new value - auto offscreenUi = DependencyManager::get(); - offscreenUi->setPinned(!_enabled); - // ensure that the the state of the menu item reflects the state of the overlay. - Menu::getInstance()->setIsOptionChecked(MenuOption::Overlays, _enabled); - _prevOverlayMenuChecked = _enabled; - - // if the new state is visible/enabled... - if (_enabled && qApp->isHMDMode()) { - centerUI(); + bool targetVisible = Menu::getInstance()->isOptionChecked(MenuOption::Overlays) && (0 == (_flags & SuppressMask)); + if (targetVisible != currentVisible) { + offscreenUi->setPinned(!targetVisible); + if (targetVisible && _hmdMode) { + centerUI(); + } } } - -bool OverlayConductor::getEnabled() const { - return _enabled; -} - diff --git a/interface/src/ui/OverlayConductor.h b/interface/src/ui/OverlayConductor.h index 375b2652f6..1bdfe2ed79 100644 --- a/interface/src/ui/OverlayConductor.h +++ b/interface/src/ui/OverlayConductor.h @@ -17,33 +17,20 @@ public: ~OverlayConductor(); void update(float dt); - void setEnabled(bool enable); - bool getEnabled() const; - void centerUI(); private: bool headOutsideOverlay() const; bool updateAvatarHasDriveInput(); bool updateAvatarIsAtRest(); - bool userWishesToHide() const; - bool userWishesToShow() const; - enum State { - Enabled = 0, - DisabledByDrive, - DisabledByHead, - DisabledByToggle, - NumStates + enum SupressionFlags { + SuppressedByDrive = 0x01, + SuppressedByHead = 0x02, + SuppressMask = 0x03, }; - void setState(State state); - State getState() const; - - State _state { DisabledByDrive }; - - bool _prevOverlayMenuChecked { true }; - bool _enabled { false }; + uint8_t _flags { SuppressedByDrive }; bool _hmdMode { false }; // used by updateAvatarHasDriveInput From 067539ff6d3bb1de82ba5ebd79542e79228ac767 Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Wed, 15 Jun 2016 17:44:58 -0700 Subject: [PATCH 65/93] Fix overlay stuck to face --- interface/src/ui/OverlayConductor.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index a455147bb8..74e27122e4 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -105,9 +105,11 @@ void OverlayConductor::update(float dt) { MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); // centerUI when hmd mode is first enabled and mounted - if (qApp->isHMDMode() && qApp->getActiveDisplayPlugin()->isDisplayVisible() && !_hmdMode) { - _hmdMode = true; - centerUI(); + if (qApp->isHMDMode() && qApp->getActiveDisplayPlugin()->isDisplayVisible()) { + if (!_hmdMode) { + _hmdMode = true; + centerUI(); + } } else { _hmdMode = false; } From 74a3db4e1c488ece88e793e020e4d62133e568af Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Fri, 17 Jun 2016 11:14:56 -0700 Subject: [PATCH 66/93] When setting Reticle.depth, don't assume that we're in the center of the HUD. --- .../controllers/handControllerPointer.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/scripts/system/controllers/handControllerPointer.js b/scripts/system/controllers/handControllerPointer.js index e87f5da946..0f1e23b45c 100644 --- a/scripts/system/controllers/handControllerPointer.js +++ b/scripts/system/controllers/handControllerPointer.js @@ -153,9 +153,8 @@ function isPointingAtOverlay(optionalHudPosition2d) { } // Generalized HUD utilities, with or without HMD: -// These two "vars" are for documentation. Do not change their values! -var SPHERICAL_HUD_DISTANCE = 1; // meters. -var PLANAR_PERPENDICULAR_HUD_DISTANCE = SPHERICAL_HUD_DISTANCE; +// This "var" is for documentation. Do not change the value! +var PLANAR_PERPENDICULAR_HUD_DISTANCE = 1; function calculateRayUICollisionPoint(position, direction) { // Answer the 3D intersection of the HUD by the given ray, or falsey if no intersection. if (HMD.active) { @@ -274,6 +273,11 @@ function expireMouseCursor(now) { Reticle.visible = false; } } +function hudReticleDistance() { // 3d distance from camera to the reticle position on hud + // (The camera is only in the center of the sphere on reset.) + var reticlePositionOnHUD = HMD.worldPointFromOverlay(Reticle.position); + return Vec3.distance(reticlePositionOnHUD, HMD.position); +} function onMouseMove() { // Display cursor at correct depth (as in depthReticle.js), and updateMouseActivity. if (ignoreMouseActivity()) { @@ -283,11 +287,10 @@ function onMouseMove() { if (HMD.active) { // set depth updateSeeking(); if (isPointingAtOverlay()) { - Reticle.setDepth(SPHERICAL_HUD_DISTANCE); // NOT CORRECT IF WE SWITCH TO OFFSET SPHERE! + Reticle.depth = hudReticleDistance(); } else { var result = findRayIntersection(Camera.computePickRay(Reticle.position.x, Reticle.position.y)); - var depth = result.intersects ? result.distance : APPARENT_MAXIMUM_DEPTH; - Reticle.setDepth(depth); + Reticle.depth = result.intersects ? result.distance : APPARENT_MAXIMUM_DEPTH; } } updateMouseActivity(); // After the above, just in case the depth movement is awkward when becoming visible. @@ -493,8 +496,8 @@ function update() { setReticlePosition(hudPoint2d); // If there's a HUD element at the (newly moved) reticle, just make it visible and bail. if (isPointingAtOverlay(hudPoint2d)) { - if (HMD.active) { // Doesn't hurt anything without the guard, but consider it documentation. - Reticle.depth = SPHERICAL_HUD_DISTANCE; // NOT CORRECT IF WE SWITCH TO OFFSET SPHERE! + if (HMD.active) { + Reticle.depth = hudReticleDistance(); } return turnOffVisualization(true); } From e04d86a75a202d9ad108c70e9266429a29ed05e2 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 17 Jun 2016 12:10:26 -0700 Subject: [PATCH 67/93] Change rendering of reticle when in HMD mode Properly take the overlay model matrix into account. Reticle depth is now relative to the eye, not relative to the overlay sphere. --- .../src/display-plugins/CompositorHelper.cpp | 35 ++++++++----------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp index 2d3c79071f..a17fe2fd49 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp @@ -409,30 +409,25 @@ void CompositorHelper::updateTooltips() { //} } +// eyePose and headPosition are in sensor space. +// the resulting matrix should be in view space. glm::mat4 CompositorHelper::getReticleTransform(const glm::mat4& eyePose, const glm::vec3& headPosition) const { glm::mat4 result; if (isHMD()) { - vec3 reticleScale = vec3(Cursor::Manager::instance().getScale() * reticleSize); - auto reticlePosition = getReticlePosition(); - auto spherical = overlayToSpherical(reticlePosition); - // The pointer transform relative to the sensor - auto pointerTransform = glm::mat4_cast(quat(vec3(-spherical.y, spherical.x, 0.0f))) * glm::translate(mat4(), vec3(0, 0, -1)); - float reticleDepth = getReticleDepth(); - if (reticleDepth != 1.0f) { - // Cursor position in UI space - auto cursorPosition = vec3(pointerTransform[3]) / pointerTransform[3].w; - // Ray to the cursor, in UI space - auto cursorRay = glm::normalize(cursorPosition - headPosition) * reticleDepth; - // Move the ray to be relative to the head pose - pointerTransform[3] = vec4(cursorRay + headPosition, 1); - // Scale up the cursor because of distance - reticleScale *= reticleDepth; + vec2 spherical = overlayToSpherical(getReticlePosition()); + vec3 overlaySurfacePoint = getPoint(spherical.x, spherical.y); // overlay space + vec3 sensorSurfacePoint = _modelTransform.transform(overlaySurfacePoint); // sensor space + vec3 d = sensorSurfacePoint - headPosition; + vec3 reticlePosition; + if (glm::length(d) >= EPSILON) { + d = glm::normalize(d); + } else { + d = glm::normalize(overlaySurfacePoint); } - glm::mat4 overlayXfm; - _modelTransform.getMatrix(overlayXfm); - pointerTransform = overlayXfm * pointerTransform; - pointerTransform = glm::inverse(eyePose) * pointerTransform; - result = glm::scale(pointerTransform, reticleScale); + reticlePosition = headPosition + (d * getReticleDepth()); + quat reticleOrientation = quat(vec3(-spherical.y, spherical.x, 0.0f)); + vec3 reticleScale = vec3(Cursor::Manager::instance().getScale() * reticleSize * getReticleDepth()); + return glm::inverse(eyePose) * createMatFromScaleQuatAndPos(reticleScale, reticleOrientation, reticlePosition); } else { static const float CURSOR_PIXEL_SIZE = 32.0f; const auto canvasSize = vec2(toGlm(_renderingWidget->size()));; From ec615caa80d66ec3e7f2544a904951ef85856522 Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Fri, 17 Jun 2016 10:56:35 -0700 Subject: [PATCH 68/93] Support toolbar API --- interface/resources/qml/AssetServer.qml | 2 +- interface/resources/qml/Browser.qml | 2 +- interface/resources/qml/InfoView.qml | 2 +- interface/resources/qml/LoginDialog.qml | 2 +- interface/resources/qml/QmlWebWindow.qml | 2 +- interface/resources/qml/ToolWindow.qml | 2 +- interface/resources/qml/UpdateDialog.qml | 2 +- .../qml/dialogs/PreferencesDialog.qml | 2 +- .../qml/dialogs/preferences/AvatarBrowser.qml | 15 +- interface/resources/qml/hifi/Desktop.qml | 46 +++-- .../resources/qml/hifi/ToggleHudButton.qml | 38 ----- .../qml/hifi/dialogs/AttachmentsDialog.qml | 2 +- .../qml/hifi/dialogs/ModelBrowserDialog.qml | 2 +- .../qml/hifi/dialogs/RunningScripts.qml | 2 +- .../qml/hifi/dialogs/SnapshotShareDialog.qml | 2 +- .../resources/qml/hifi/toolbars/Toolbar.qml | 117 +++++++++++++ .../qml/hifi/toolbars/ToolbarButton.qml | 39 +++++ interface/resources/qml/windows/Frame.qml | 2 +- .../resources/qml/windows/ModalWindow.qml | 2 +- .../resources/qml/windows/ScrollingWindow.qml | 157 ++++++++++++++++++ interface/resources/qml/windows/ToolFrame.qml | 49 ++++++ interface/resources/qml/windows/Window.qml | 146 ++-------------- interface/src/Application.cpp | 3 + .../scripting/ToolbarScriptingInterface.cpp | 124 ++++++++++++++ .../src/scripting/ToolbarScriptingInterface.h | 26 +++ scripts/defaultScripts.js | 3 +- scripts/developer/tests/toolbarTest.js | 118 +++++++++++++ .../system/assets/images/tools/microphone.svg | 13 ++ scripts/system/examples.js | 98 ++--------- scripts/system/goto.js | 42 ++--- scripts/system/hmd.js | 55 +++--- scripts/system/mute.js | 29 ++++ tests/ui/qml/Stubs.qml | 12 ++ tests/ui/qml/main.qml | 89 +++++++--- tests/ui/qmlscratch.pro | 1 + tests/ui/src/main.cpp | 3 + 36 files changed, 870 insertions(+), 381 deletions(-) delete mode 100644 interface/resources/qml/hifi/ToggleHudButton.qml create mode 100644 interface/resources/qml/hifi/toolbars/Toolbar.qml create mode 100644 interface/resources/qml/hifi/toolbars/ToolbarButton.qml create mode 100644 interface/resources/qml/windows/ScrollingWindow.qml create mode 100644 interface/resources/qml/windows/ToolFrame.qml create mode 100644 interface/src/scripting/ToolbarScriptingInterface.cpp create mode 100644 interface/src/scripting/ToolbarScriptingInterface.h create mode 100644 scripts/developer/tests/toolbarTest.js create mode 100644 scripts/system/assets/images/tools/microphone.svg create mode 100644 scripts/system/mute.js diff --git a/interface/resources/qml/AssetServer.qml b/interface/resources/qml/AssetServer.qml index 5b02020272..c9b6305258 100644 --- a/interface/resources/qml/AssetServer.qml +++ b/interface/resources/qml/AssetServer.qml @@ -18,7 +18,7 @@ import "controls-uit" as HifiControls import "windows" import "dialogs" -Window { +ScrollingWindow { id: root objectName: "AssetServer" title: "Asset Browser" diff --git a/interface/resources/qml/Browser.qml b/interface/resources/qml/Browser.qml index 01e1bb7680..8c8cf05444 100644 --- a/interface/resources/qml/Browser.qml +++ b/interface/resources/qml/Browser.qml @@ -6,7 +6,7 @@ import "controls-uit" import "styles-uit" import "windows" -Window { +ScrollingWindow { id: root HifiConstants { id: hifi } title: "Browser" diff --git a/interface/resources/qml/InfoView.qml b/interface/resources/qml/InfoView.qml index 0f17a88614..f18969fb2f 100644 --- a/interface/resources/qml/InfoView.qml +++ b/interface/resources/qml/InfoView.qml @@ -14,7 +14,7 @@ import Hifi 1.0 as Hifi import "controls-uit" import "windows" as Windows -Windows.Window { +Windows.ScrollingWindow { id: root width: 800 height: 800 diff --git a/interface/resources/qml/LoginDialog.qml b/interface/resources/qml/LoginDialog.qml index f5030cb88d..f75e83e36e 100644 --- a/interface/resources/qml/LoginDialog.qml +++ b/interface/resources/qml/LoginDialog.qml @@ -14,7 +14,7 @@ import "controls" import "styles" import "windows" -Window { +ScrollingWindow { id: root HifiConstants { id: hifi } objectName: "LoginDialog" diff --git a/interface/resources/qml/QmlWebWindow.qml b/interface/resources/qml/QmlWebWindow.qml index 09c3bd7f28..542b44b95e 100644 --- a/interface/resources/qml/QmlWebWindow.qml +++ b/interface/resources/qml/QmlWebWindow.qml @@ -17,7 +17,7 @@ import "windows" as Windows import "controls-uit" as Controls import "styles-uit" -Windows.Window { +Windows.ScrollingWindow { id: root HifiConstants { id: hifi } title: "WebWindow" diff --git a/interface/resources/qml/ToolWindow.qml b/interface/resources/qml/ToolWindow.qml index faa96fac5a..bbfc74493d 100644 --- a/interface/resources/qml/ToolWindow.qml +++ b/interface/resources/qml/ToolWindow.qml @@ -19,7 +19,7 @@ import "windows" import "controls-uit" import "styles-uit" -Window { +ScrollingWindow { id: toolWindow resizable: true objectName: "ToolWindow" diff --git a/interface/resources/qml/UpdateDialog.qml b/interface/resources/qml/UpdateDialog.qml index 52fdab25f9..91dc210eda 100644 --- a/interface/resources/qml/UpdateDialog.qml +++ b/interface/resources/qml/UpdateDialog.qml @@ -7,7 +7,7 @@ import "controls-uit" import "styles-uit" import "windows" -Window { +ScrollingWindow { id: root HifiConstants { id: hifi } objectName: "UpdateDialog" diff --git a/interface/resources/qml/dialogs/PreferencesDialog.qml b/interface/resources/qml/dialogs/PreferencesDialog.qml index 398e0abd8e..5278118a22 100644 --- a/interface/resources/qml/dialogs/PreferencesDialog.qml +++ b/interface/resources/qml/dialogs/PreferencesDialog.qml @@ -16,7 +16,7 @@ import "../styles-uit" import "../windows" import "preferences" -Window { +ScrollingWindow { id: root title: "Preferences" resizable: true diff --git a/interface/resources/qml/dialogs/preferences/AvatarBrowser.qml b/interface/resources/qml/dialogs/preferences/AvatarBrowser.qml index 90d2dc5284..16d25b3c4c 100644 --- a/interface/resources/qml/dialogs/preferences/AvatarBrowser.qml +++ b/interface/resources/qml/dialogs/preferences/AvatarBrowser.qml @@ -23,15 +23,10 @@ Windows.Window { resizable: true modality: Qt.ApplicationModal - Item { - width: pane.contentWidth - implicitHeight: pane.scrollHeight - - Controls.WebView { - id: webview - anchors.fill: parent - url: "https://metaverse.highfidelity.com/marketplace?category=avatars" - focus: true - } + Controls.WebView { + id: webview + anchors.fill: parent + url: "https://metaverse.highfidelity.com/marketplace?category=avatars" + focus: true } } diff --git a/interface/resources/qml/hifi/Desktop.qml b/interface/resources/qml/hifi/Desktop.qml index e127a235e6..c14d55cb00 100644 --- a/interface/resources/qml/hifi/Desktop.qml +++ b/interface/resources/qml/hifi/Desktop.qml @@ -1,10 +1,12 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 import QtWebEngine 1.1; +import Qt.labs.settings 1.0 import "../desktop" import ".." import "." +import "./toolbars" Desktop { id: desktop @@ -20,13 +22,6 @@ Desktop { acceptedButtons: Qt.NoButton } - Component.onCompleted: { - WebEngine.settings.javascriptCanOpenWindows = true; - WebEngine.settings.javascriptCanAccessClipboard = false; - WebEngine.settings.spatialNavigationEnabled = false; - WebEngine.settings.localContentCanAccessRemoteUrls = true; - } - // The tool window, one instance property alias toolWindow: toolWindow ToolWindow { id: toolWindow } @@ -49,11 +44,40 @@ Desktop { } } + property var toolbars: ({}) + Component { id: toolbarBuilder; Toolbar { } } - ToggleHudButton { - anchors.bottom: parent.bottom - anchors.bottomMargin: 32 - anchors.horizontalCenter: parent.horizontalCenter + Component.onCompleted: { + WebEngine.settings.javascriptCanOpenWindows = true; + WebEngine.settings.javascriptCanAccessClipboard = false; + WebEngine.settings.spatialNavigationEnabled = false; + WebEngine.settings.localContentCanAccessRemoteUrls = true; + + var sysToolbar = desktop.getToolbar("com.highfidelity.interface.toolbar.system"); + //toolbars[sysToolbar.objectName] = sysToolbar + var toggleHudButton = sysToolbar.addButton({ + imageURL: "../../../icons/hud-01.svg", + visible: true, + + }); + toggleHudButton.yOffset = Qt.binding(function(){ + return desktop.pinned ? 50 : 0 + }); + toggleHudButton.clicked.connect(function(){ + console.log("Clicked on hud button") + var overlayMenuItem = "Overlays" + MenuInterface.setIsOptionChecked(overlayMenuItem, !MenuInterface.isOptionChecked(overlayMenuItem)); + }); + } + + // Create or fetch a toolbar with the given name + function getToolbar(name) { + var result = toolbars[name]; + if (!result) { + result = toolbars[name] = toolbarBuilder.createObject(desktop, {}); + result.objectName = name; + } + return result; } } diff --git a/interface/resources/qml/hifi/ToggleHudButton.qml b/interface/resources/qml/hifi/ToggleHudButton.qml deleted file mode 100644 index 63c056b352..0000000000 --- a/interface/resources/qml/hifi/ToggleHudButton.qml +++ /dev/null @@ -1,38 +0,0 @@ -import QtQuick 2.5 -import QtQuick.Controls 1.4 - -import "../windows" - -Window { - //frame: HiddenFrame {} - hideBackground: true - resizable: false - destroyOnCloseButton: false - destroyOnHidden: false - closable: false - shown: true - pinned: true - width: 50 - height: 50 - clip: true - visible: true - // Disable this window from being able to call 'desktop.raise() and desktop.showDesktop' - activator: Item {} - - Item { - width: 50 - height: 50 - Image { - y: desktop.pinned ? -50 : 0 - id: hudToggleImage - source: "../../icons/hud-01.svg" - } - MouseArea { - readonly property string overlayMenuItem: "Overlays" - anchors.fill: parent - onClicked: MenuInterface.setIsOptionChecked(overlayMenuItem, !MenuInterface.isOptionChecked(overlayMenuItem)); - } - } -} - - diff --git a/interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml b/interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml index 7b3a368bb0..15467f8021 100755 --- a/interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml +++ b/interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml @@ -9,7 +9,7 @@ import "../../controls-uit" as HifiControls import "../../windows" import "attachments" -Window { +ScrollingWindow { id: root title: "Attachments" objectName: "AttachmentsDialog" diff --git a/interface/resources/qml/hifi/dialogs/ModelBrowserDialog.qml b/interface/resources/qml/hifi/dialogs/ModelBrowserDialog.qml index 29f0498f59..aeffb8e4bf 100644 --- a/interface/resources/qml/hifi/dialogs/ModelBrowserDialog.qml +++ b/interface/resources/qml/hifi/dialogs/ModelBrowserDialog.qml @@ -11,7 +11,7 @@ import "../../styles-uit" import "../../controls-uit" as HifiControls import "../../windows" -Window { +ScrollingWindow { id: root resizable: true width: 600 diff --git a/interface/resources/qml/hifi/dialogs/RunningScripts.qml b/interface/resources/qml/hifi/dialogs/RunningScripts.qml index ccf2413421..5457caccf1 100644 --- a/interface/resources/qml/hifi/dialogs/RunningScripts.qml +++ b/interface/resources/qml/hifi/dialogs/RunningScripts.qml @@ -17,7 +17,7 @@ import "../../styles-uit" import "../../controls-uit" as HifiControls import "../../windows" -Window { +ScrollingWindow { id: root objectName: "RunningScripts" title: "Running Scripts" diff --git a/interface/resources/qml/hifi/dialogs/SnapshotShareDialog.qml b/interface/resources/qml/hifi/dialogs/SnapshotShareDialog.qml index f99b770a78..3dacb3b39c 100644 --- a/interface/resources/qml/hifi/dialogs/SnapshotShareDialog.qml +++ b/interface/resources/qml/hifi/dialogs/SnapshotShareDialog.qml @@ -7,7 +7,7 @@ import "../../windows" import "../../js/Utils.js" as Utils import "../models" -Window { +ScrollingWindow { id: root resizable: true width: 516 diff --git a/interface/resources/qml/hifi/toolbars/Toolbar.qml b/interface/resources/qml/hifi/toolbars/Toolbar.qml new file mode 100644 index 0000000000..35c816569b --- /dev/null +++ b/interface/resources/qml/hifi/toolbars/Toolbar.qml @@ -0,0 +1,117 @@ +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import Qt.labs.settings 1.0 + +import "../../windows" +import "." + +Window { + id: window + frame: ToolFrame { } + hideBackground: true + resizable: false + destroyOnCloseButton: false + destroyOnHidden: false + closable: false + shown: true + pinned: true + width: content.width + height: content.height + visible: true + // Disable this window from being able to call 'desktop.raise() and desktop.showDesktop' + activator: Item {} + property bool horizontal: true + property real buttonSize: 50; + property var buttons: [] + property var container: horizontal ? row : column + + Settings { + category: "toolbar/" + window.objectName + property alias x: window.x + property alias y: window.y + } + + onHorizontalChanged: { + var oldParent = horizontal ? column : row; + var newParent = horizontal ? row : column; + var move = []; + + var i; + for (i in oldParent.children) { + var child = oldParent.children[i]; + if (child.spacer) { + continue; + } + move.push(oldParent.children[i]); + } + for (i in move) { + move[i].parent = newParent; + if (horizontal) { + move[i].y = 0 + } else { + move[i].x = 0 + } + } + fixSpacers(); + } + + Item { + id: content + implicitHeight: horizontal ? row.height : column.height + implicitWidth: horizontal ? row.width : column.width + Row { + id: row + spacing: 6 + visible: window.horizontal + Rectangle{ readonly property bool spacer: true; id: rowSpacer1; width: 1; height: row.height } + Rectangle{ readonly property bool spacer: true; id: rowSpacer2; width: 1; height: row.height } + Rectangle{ readonly property bool spacer: true; id: rowSpacer3; width: 1; height: row.height } + Rectangle{ readonly property bool spacer: true; id: rowSpacer4; width: 1; height: row.height } + } + + Column { + id: column + spacing: 6 + visible: !window.horizontal + Rectangle{ readonly property bool spacer: true; id: colSpacer1; width: column.width; height: 1 } + Rectangle{ readonly property bool spacer: true; id: colSpacer2; width: column.width; height: 1 } + Rectangle{ readonly property bool spacer: true; id: colSpacer3; width: column.width; height: 1 } + Rectangle{ readonly property bool spacer: true; id: colSpacer4; width: column.width; height: 1 } + } + + Component { id: toolbarButtonBuilder; ToolbarButton { } } + } + + function addButton(properties) { + properties = properties || {} + + // If a name is specified, then check if there's an existing button with that name + // and return it if so. This will allow multiple clients to listen to a single button, + // and allow scripts to be idempotent so they don't duplicate buttons if they're reloaded + if (properties.objectName) { + for (var i in buttons) { + var child = buttons[i]; + if (child.objectName === properties.objectName) { + return child; + } + } + } + + properties.toolbar = this; + var result = toolbarButtonBuilder.createObject(container, properties); + buttons.push(result); + fixSpacers(); + return result; + } + + function fixSpacers() { + colSpacer3.parent = null + colSpacer4.parent = null + rowSpacer3.parent = null + rowSpacer4.parent = null + colSpacer3.parent = column + colSpacer4.parent = column + rowSpacer3.parent = row + rowSpacer4.parent = row + } +} diff --git a/interface/resources/qml/hifi/toolbars/ToolbarButton.qml b/interface/resources/qml/hifi/toolbars/ToolbarButton.qml new file mode 100644 index 0000000000..a8514689e8 --- /dev/null +++ b/interface/resources/qml/hifi/toolbars/ToolbarButton.qml @@ -0,0 +1,39 @@ +import QtQuick 2.5 +import QtQuick.Controls 1.4 + +Item { + id: button + property alias imageURL: image.source + property alias alpha: button.opacity + property var subImage; + property int yOffset: 0 + property var toolbar; + property real size: 50 // toolbar ? toolbar.buttonSize : 50 + width: size; height: size + clip: true + + Component.onCompleted: { + if (subImage) { + if (subImage.y) { + yOffset = subImage.y; + } + } + } + + signal clicked() + + Image { + id: image + y: -button.yOffset; + width: parent.width + } + + MouseArea { + anchors.fill: parent + onClicked: { + console.log("Clicked on button " + image.source + " named " + button.objectName) + button.clicked(); + } + } +} + diff --git a/interface/resources/qml/windows/Frame.qml b/interface/resources/qml/windows/Frame.qml index 9519a44cf0..bc8ecc35ec 100644 --- a/interface/resources/qml/windows/Frame.qml +++ b/interface/resources/qml/windows/Frame.qml @@ -80,7 +80,7 @@ Item { border.width: 3 border.color: hifi.colors.white50 radius: hifi.dimensions.borderRadius - visible: window ? !pane.visible : false + visible: window ? !window.content.visible : false } MouseArea { diff --git a/interface/resources/qml/windows/ModalWindow.qml b/interface/resources/qml/windows/ModalWindow.qml index f79dc9084f..2d56099051 100644 --- a/interface/resources/qml/windows/ModalWindow.qml +++ b/interface/resources/qml/windows/ModalWindow.qml @@ -12,7 +12,7 @@ import QtQuick 2.5 import "." -Window { +ScrollingWindow { id: window modality: Qt.ApplicationModal destroyOnCloseButton: true diff --git a/interface/resources/qml/windows/ScrollingWindow.qml b/interface/resources/qml/windows/ScrollingWindow.qml new file mode 100644 index 0000000000..f1dc744344 --- /dev/null +++ b/interface/resources/qml/windows/ScrollingWindow.qml @@ -0,0 +1,157 @@ +// +// Window.qml +// +// Created by Bradley Austin Davis on 12 Jan 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 +import QtGraphicalEffects 1.0 + +import "." +import "../styles-uit" + +// FIXME how do I set the initial position of a window without +// overriding places where the a individual client of the window +// might be setting the position with a Settings{} element? + +// FIXME how to I enable dragging without allowing the window to lay outside +// of the desktop? How do I ensure when the desktop resizes all the windows +// are still at least partially visible? +Window { + id: window + HifiConstants { id: hifi } + children: [ swallower, frame, pane, activator ] + + property var footer: Item { } // Optional static footer at the bottom of the dialog. + + // Scrollable window content. + // FIXME this should not define any visual content in this type. The base window + // type should only consist of logic sized areas, with nothing drawn (although the + // default value for the frame property does include visual decorations) + property var pane: Item { + property bool isScrolling: scrollView.height < scrollView.contentItem.height + property int contentWidth: scrollView.width - (isScrolling ? 10 : 0) + property int scrollHeight: scrollView.height + + anchors.fill: parent + anchors.rightMargin: isScrolling ? 11 : 0 + + Rectangle { + id: contentBackground + anchors.fill: parent + anchors.rightMargin: parent.isScrolling ? 11 : 0 + color: hifi.colors.baseGray + visible: !window.hideBackground && modality != Qt.ApplicationModal + } + + + LinearGradient { + visible: !window.hideBackground && gradientsSupported && modality != Qt.ApplicationModal + anchors.top: contentBackground.bottom + anchors.left: contentBackground.left + width: contentBackground.width - 1 + height: 4 + start: Qt.point(0, 0) + end: Qt.point(0, 4) + gradient: Gradient { + GradientStop { position: 0.0; color: hifi.colors.darkGray } + GradientStop { position: 1.0; color: hifi.colors.darkGray0 } + } + cached: true + } + + ScrollView { + id: scrollView + contentItem: content + horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff + verticalScrollBarPolicy: Qt.ScrollBarAsNeeded + anchors.fill: parent + anchors.rightMargin: parent.isScrolling ? 1 : 0 + anchors.bottomMargin: footer.height > 0 ? footerPane.height : 0 + + style: ScrollViewStyle { + + padding.right: -7 // Move to right away from content. + + handle: Item { + implicitWidth: 8 + Rectangle { + radius: 4 + color: hifi.colors.white30 + anchors { + fill: parent + leftMargin: 2 // Finesse size and position. + topMargin: 1 + bottomMargin: 1 + } + } + } + + scrollBarBackground: Item { + implicitWidth: 10 + Rectangle { + color: hifi.colors.darkGray30 + radius: 4 + anchors { + fill: parent + topMargin: -1 // Finesse size + bottomMargin: -2 + } + } + } + + incrementControl: Item { + visible: false + } + + decrementControl: Item { + visible: false + } + } + } + + Rectangle { + // Optional non-scrolling footer. + id: footerPane + anchors { + left: parent.left + bottom: parent.bottom + } + width: parent.contentWidth + height: footer.height + 2 * hifi.dimensions.contentSpacing.y + 3 + color: hifi.colors.baseGray + visible: footer.height > 0 + + Item { + // Horizontal rule. + anchors.fill: parent + + Rectangle { + width: parent.width + height: 1 + y: 1 // Stop displaying content just above horizontal rule/=. + color: hifi.colors.baseGrayShadow + } + + Rectangle { + width: parent.width + height: 1 + y: 2 + color: hifi.colors.baseGrayHighlight + } + } + + Item { + anchors.fill: parent + anchors.topMargin: 3 // Horizontal rule. + children: [ footer ] + } + } + } +} diff --git a/interface/resources/qml/windows/ToolFrame.qml b/interface/resources/qml/windows/ToolFrame.qml new file mode 100644 index 0000000000..ac1093092e --- /dev/null +++ b/interface/resources/qml/windows/ToolFrame.qml @@ -0,0 +1,49 @@ +// +// DefaultFrame.qml +// +// Created by Bradley Austin Davis on 12 Jan 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import QtGraphicalEffects 1.0 + +import "." +import "../styles-uit" + +Frame { + HifiConstants { id: hifi } + + Rectangle { + // Dialog frame + id: frameContent + readonly property int frameMargin: 6 + readonly property int frameMarginLeft: frameMargin + readonly property int frameMarginRight: frameMargin + readonly property int frameMarginTop: frameMargin + readonly property int frameMarginBottom: frameMargin + anchors { + topMargin: -frameMargin + leftMargin: -frameMargin + rightMargin: -frameMargin + bottomMargin: -frameMargin + } + anchors.fill: parent + color: hifi.colors.baseGrayHighlight40 + border { + width: hifi.dimensions.borderWidth + color: hifi.colors.faintGray50 + } + radius: hifi.dimensions.borderRadius / 2 + + // Enable dragging of the window + MouseArea { + anchors.fill: parent + drag.target: window + } + } +} + diff --git a/interface/resources/qml/windows/Window.qml b/interface/resources/qml/windows/Window.qml index ed1b820fc8..e3e70c1e74 100644 --- a/interface/resources/qml/windows/Window.qml +++ b/interface/resources/qml/windows/Window.qml @@ -41,7 +41,7 @@ Fadable { implicitHeight: content ? content.height : 0 implicitWidth: content ? content.width : 0 x: desktop.invalid_position; y: desktop.invalid_position; - children: [ swallower, frame, pane, activator ] + children: [ swallower, frame, content, activator ] // // Custom properties @@ -106,10 +106,10 @@ Fadable { // in the window and have a higher Z-order than the content, but follow // the position and size of frame decoration property var activator: MouseArea { - width: frame.decoration.width - height: frame.decoration.height - x: frame.decoration.anchors.leftMargin - y: frame.decoration.anchors.topMargin + width: frame.decoration ? frame.decoration.width : window.width + height: frame.decoration ? frame.decoration.height : window.height + x: frame.decoration ? frame.decoration.anchors.leftMargin : 0 + y: frame.decoration ? frame.decoration.anchors.topMargin : 0 propagateComposedEvents: true acceptedButtons: Qt.AllButtons enabled: window.visible @@ -124,10 +124,10 @@ Fadable { // to prevent things like mouse wheel events from reaching the application and changing // the camera if the user is scrolling through a list and gets to the end. property var swallower: MouseArea { - width: frame.decoration.width - height: frame.decoration.height - x: frame.decoration.anchors.leftMargin - y: frame.decoration.anchors.topMargin + width: frame.decoration ? frame.decoration.width : window.width + height: frame.decoration ? frame.decoration.height : window.height + x: frame.decoration ? frame.decoration.anchors.leftMargin : 0 + y: frame.decoration ? frame.decoration.anchors.topMargin : 0 hoverEnabled: true acceptedButtons: Qt.AllButtons enabled: window.visible @@ -140,133 +140,11 @@ Fadable { // Default to a standard frame. Can be overriden to provide custom // frame styles, like a full desktop frame to simulate a modal window - property var frame: DefaultFrame { } - - // Scrollable window content. - // FIXME this should not define any visual content in this type. The base window - // type should only consist of logic sized areas, with nothing drawn (although the - // default value for the frame property does include visual decorations) - property var pane: Item { - property bool isScrolling: scrollView.height < scrollView.contentItem.height - property int contentWidth: scrollView.width - (isScrolling ? 10 : 0) - property int scrollHeight: scrollView.height - - anchors.fill: parent - anchors.rightMargin: isScrolling ? 11 : 0 - - Rectangle { - id: contentBackground - anchors.fill: parent - anchors.rightMargin: parent.isScrolling ? 11 : 0 - color: hifi.colors.baseGray - visible: !window.hideBackground && modality != Qt.ApplicationModal - } - - - LinearGradient { - visible: !window.hideBackground && gradientsSupported && modality != Qt.ApplicationModal - anchors.top: contentBackground.bottom - anchors.left: contentBackground.left - width: contentBackground.width - 1 - height: 4 - start: Qt.point(0, 0) - end: Qt.point(0, 4) - gradient: Gradient { - GradientStop { position: 0.0; color: hifi.colors.darkGray } - GradientStop { position: 1.0; color: hifi.colors.darkGray0 } - } - cached: true - } - - ScrollView { - id: scrollView - contentItem: content - horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff - verticalScrollBarPolicy: Qt.ScrollBarAsNeeded - anchors.fill: parent - anchors.rightMargin: parent.isScrolling ? 1 : 0 - anchors.bottomMargin: footer.height > 0 ? footerPane.height : 0 - - style: ScrollViewStyle { - - padding.right: -7 // Move to right away from content. - - handle: Item { - implicitWidth: 8 - Rectangle { - radius: 4 - color: hifi.colors.white30 - anchors { - fill: parent - leftMargin: 2 // Finesse size and position. - topMargin: 1 - bottomMargin: 1 - } - } - } - - scrollBarBackground: Item { - implicitWidth: 10 - Rectangle { - color: hifi.colors.darkGray30 - radius: 4 - anchors { - fill: parent - topMargin: -1 // Finesse size - bottomMargin: -2 - } - } - } - - incrementControl: Item { - visible: false - } - - decrementControl: Item { - visible: false - } - } - } - - Rectangle { - // Optional non-scrolling footer. - id: footerPane - anchors { - left: parent.left - bottom: parent.bottom - } - width: parent.contentWidth - height: footer.height + 2 * hifi.dimensions.contentSpacing.y + 3 - color: hifi.colors.baseGray - visible: footer.height > 0 - - Item { - // Horizontal rule. - anchors.fill: parent - - Rectangle { - width: parent.width - height: 1 - y: 1 // Stop displaying content just above horizontal rule/=. - color: hifi.colors.baseGrayShadow - } - - Rectangle { - width: parent.width - height: 1 - y: 2 - color: hifi.colors.baseGrayHighlight - } - } - - Item { - anchors.fill: parent - anchors.topMargin: 3 // Horizontal rule. - children: [ footer ] - } - } + property var frame: DefaultFrame { + //window: window } + // // Handlers // diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index ee365cd5a1..6dc575b572 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -133,6 +133,7 @@ #include "scripting/WebWindowClass.h" #include "scripting/WindowScriptingInterface.h" #include "scripting/ControllerScriptingInterface.h" +#include "scripting/ToolbarScriptingInterface.h" #include "scripting/RatesScriptingInterface.h" #if defined(Q_OS_MAC) || defined(Q_OS_WIN) #include "SpeechRecognizer.h" @@ -437,6 +438,7 @@ bool setupEssentials(int& argc, char** argv) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(); #if defined(Q_OS_MAC) || defined(Q_OS_WIN) @@ -4527,6 +4529,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri RayToOverlayIntersectionResultFromScriptValue); scriptEngine->registerGlobalObject("Desktop", DependencyManager::get().data()); + scriptEngine->registerGlobalObject("Toolbars", DependencyManager::get().data()); scriptEngine->registerGlobalObject("Window", DependencyManager::get().data()); scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter, diff --git a/interface/src/scripting/ToolbarScriptingInterface.cpp b/interface/src/scripting/ToolbarScriptingInterface.cpp new file mode 100644 index 0000000000..fd96b6a809 --- /dev/null +++ b/interface/src/scripting/ToolbarScriptingInterface.cpp @@ -0,0 +1,124 @@ +// +// Created by Bradley Austin Davis on 2016-06-16 +// Copyright 2013-2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ToolbarScriptingInterface.h" + +#include + +class QmlWrapper : public QObject { + Q_OBJECT +public: + QmlWrapper(QObject* qmlObject, QObject* parent = nullptr) + : QObject(parent), _qmlObject(qmlObject) { + + const QMetaObject *metaobject = qmlObject->metaObject(); + int count = metaobject->propertyCount(); + qDebug() << "Scanning properties for " << qmlObject; + for (int i = 0; i < count; ++i) { + QMetaProperty metaproperty = metaobject->property(i); + const char *name = metaproperty.name(); + qDebug() << "Property " << name; + } + } + + Q_INVOKABLE void writeProperty(QString propertyName, QVariant propertyValue) { + auto offscreenUi = DependencyManager::get(); + offscreenUi->executeOnUiThread([=] { + _qmlObject->setProperty(propertyName.toStdString().c_str(), propertyValue); + }); + } + + Q_INVOKABLE void writeProperties(QVariant propertyMap) { + auto offscreenUi = DependencyManager::get(); + offscreenUi->executeOnUiThread([=] { + QVariantMap map = propertyMap.toMap(); + for (const QString& key : map.keys()) { + _qmlObject->setProperty(key.toStdString().c_str(), map[key]); + } + }); + } + + Q_INVOKABLE QVariant readProperty(const QString& propertyName) { + auto offscreenUi = DependencyManager::get(); + return offscreenUi->returnFromUiThread([&]()->QVariant { + return _qmlObject->property(propertyName.toStdString().c_str()); + }); + } + + Q_INVOKABLE QVariant readProperties(const QVariant& propertyList) { + auto offscreenUi = DependencyManager::get(); + return offscreenUi->returnFromUiThread([&]()->QVariant { + QVariantMap result; + for (const QVariant& property : propertyList.toList()) { + QString propertyString = property.toString(); + result.insert(propertyString, _qmlObject->property(propertyString.toStdString().c_str())); + } + return result; + }); + } + + +protected: + QObject* _qmlObject{ nullptr }; +}; + + +class ToolbarButtonProxy : public QmlWrapper { + Q_OBJECT + +public: + ToolbarButtonProxy(QObject* qmlObject, QObject* parent = nullptr) : QmlWrapper(qmlObject, parent) { + connect(qmlObject, SIGNAL(clicked()), this, SIGNAL(clicked())); + } + +signals: + void clicked(); +}; + +class ToolbarProxy : public QmlWrapper { + Q_OBJECT + +public: + ToolbarProxy(QObject* qmlObject, QObject* parent = nullptr) : QmlWrapper(qmlObject, parent) { } + + Q_INVOKABLE QObject* addButton(const QVariant& properties) { + QVariant resultVar; + bool invokeResult = QMetaObject::invokeMethod(_qmlObject, "addButton", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QVariant, resultVar), Q_ARG(QVariant, properties)); + if (!invokeResult) { + return nullptr; + } + + QObject* rawButton = qvariant_cast(resultVar); + if (!rawButton) { + return nullptr; + } + + return new ToolbarButtonProxy(rawButton, this); + } +}; + + +QObject* ToolbarScriptingInterface::getToolbar(const QString& toolbarId) { + auto offscreenUi = DependencyManager::get(); + auto desktop = offscreenUi->getDesktop(); + QVariant resultVar; + bool invokeResult = QMetaObject::invokeMethod(desktop, "getToolbar", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QVariant, resultVar), Q_ARG(QVariant, toolbarId)); + if (!invokeResult) { + return nullptr; + } + + QObject* rawToolbar = qvariant_cast(resultVar); + if (!rawToolbar) { + return nullptr; + } + + return new ToolbarProxy(rawToolbar); +} + + +#include "ToolbarScriptingInterface.moc" \ No newline at end of file diff --git a/interface/src/scripting/ToolbarScriptingInterface.h b/interface/src/scripting/ToolbarScriptingInterface.h new file mode 100644 index 0000000000..d3706cb6e1 --- /dev/null +++ b/interface/src/scripting/ToolbarScriptingInterface.h @@ -0,0 +1,26 @@ +// +// Created by Bradley Austin Davis on 2016-06-16 +// Copyright 2013-2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_ToolbarScriptingInterface_h +#define hifi_ToolbarScriptingInterface_h + +#include + +#include + +#include + +class ToolbarProxy; + +class ToolbarScriptingInterface : public QObject, public Dependency { + Q_OBJECT +public: + Q_INVOKABLE QObject* getToolbar(const QString& toolbarId); +}; + +#endif // hifi_ToolbarScriptingInterface_h diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index 218cab8903..bbbe049f39 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -12,9 +12,10 @@ Script.load("system/progress.js"); Script.load("system/away.js"); Script.load("system/users.js"); -Script.load("system/examples.js"); +Script.load("system/mute.js"); Script.load("system/goto.js"); Script.load("system/hmd.js"); +Script.load("system/examples.js"); Script.load("system/edit.js"); Script.load("system/selectAudioDevice.js"); Script.load("system/notifications.js"); diff --git a/scripts/developer/tests/toolbarTest.js b/scripts/developer/tests/toolbarTest.js new file mode 100644 index 0000000000..20f349929f --- /dev/null +++ b/scripts/developer/tests/toolbarTest.js @@ -0,0 +1,118 @@ +var isActive = false; + +var toolBar = (function() { + var that = {}, + toolBar, + activeButton, + newModelButton, + newCubeButton, + newSphereButton, + newLightButton, + newTextButton, + newWebButton, + newZoneButton, + newParticleButton + + var toolIconUrl = Script.resolvePath("assets/images/tools/"); + + function initialize() { + print("Toolbars: " + Toolbars); + toolBar = Toolbars.getToolbar("highfidelity.edit.toolbar"); + print("Toolbar: " + toolBar); + activeButton = toolBar.addButton({ + objectName: "activeButton", + imageURL: toolIconUrl + "edit-01.svg", + visible: true, + alpha: 0.9, + }); + + print("Button " + activeButton); + print("Button signal " + activeButton.clicked); + activeButton.clicked.connect(function(){ + print("Clicked on button " + isActive); + that.setActive(!isActive); + }); + + newModelButton = toolBar.addButton({ + objectName: "newModelButton", + imageURL: toolIconUrl + "model-01.svg", + alpha: 0.9, + visible: false + }); + + newCubeButton = toolBar.addButton({ + objectName: "newCubeButton", + imageURL: toolIconUrl + "cube-01.svg", + alpha: 0.9, + visible: false + }); + + newSphereButton = toolBar.addButton({ + objectName: "newSphereButton", + imageURL: toolIconUrl + "sphere-01.svg", + alpha: 0.9, + visible: false + }); + + newLightButton = toolBar.addButton({ + objectName: "newLightButton", + imageURL: toolIconUrl + "light-01.svg", + alpha: 0.9, + visible: false + }); + + newTextButton = toolBar.addButton({ + objectName: "newTextButton", + imageURL: toolIconUrl + "text-01.svg", + alpha: 0.9, + visible: false + }); + + newWebButton = toolBar.addButton({ + objectName: "newWebButton", + imageURL: toolIconUrl + "web-01.svg", + alpha: 0.9, + visible: false + }); + + newZoneButton = toolBar.addButton({ + objectName: "newZoneButton", + imageURL: toolIconUrl + "zone-01.svg", + alpha: 0.9, + visible: false + }); + + newParticleButton = toolBar.addButton({ + objectName: "newParticleButton", + imageURL: toolIconUrl + "particle-01.svg", + alpha: 0.9, + visible: false + }); + + that.setActive(false); + newModelButton.clicked(); + } + + that.setActive = function(active) { + if (active != isActive) { + isActive = active; + that.showTools(isActive); + } + }; + + // Sets visibility of tool buttons, excluding the power button + that.showTools = function(doShow) { + newModelButton.writeProperty('visible', doShow); + newCubeButton.writeProperty('visible', doShow); + newSphereButton.writeProperty('visible', doShow); + newLightButton.writeProperty('visible', doShow); + newTextButton.writeProperty('visible', doShow); + newWebButton.writeProperty('visible', doShow); + newZoneButton.writeProperty('visible', doShow); + newModelButton.writeProperty('visible', doShow); + newParticleButton.writeProperty('visible', doShow); + }; + + initialize(); + return that; +}()); diff --git a/scripts/system/assets/images/tools/microphone.svg b/scripts/system/assets/images/tools/microphone.svg new file mode 100644 index 0000000000..bd5e8afac7 --- /dev/null +++ b/scripts/system/assets/images/tools/microphone.svg @@ -0,0 +1,13 @@ + + + image/svg+xml + + + Layer 1 + + + + Mute + + + \ No newline at end of file diff --git a/scripts/system/examples.js b/scripts/system/examples.js index 6f4268182c..f850a9789e 100644 --- a/scripts/system/examples.js +++ b/scripts/system/examples.js @@ -9,10 +9,6 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -Script.include([ - "libraries/toolBars.js", -]); - var toolIconUrl = Script.resolvePath("assets/images/tools/"); var EXAMPLES_URL = "https://metaverse.highfidelity.com/examples"; @@ -52,87 +48,21 @@ function toggleExamples() { } } -var toolBar = (function() { - var that = {}, - toolBar, - browseExamplesButton; +var toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system"); - function initialize() { - toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL, "highfidelity.examples.toolbar", function(windowDimensions, toolbar) { - return { - x: (windowDimensions.x / 2) + (Tool.IMAGE_WIDTH * 2), - y: windowDimensions.y - }; - }, { - x: -toolWidth / 2, - y: -TOOLBAR_MARGIN_Y - toolHeight - }); - browseExamplesButton = toolBar.addTool({ - imageURL: toolIconUrl + "examples-01.svg", - subImage: { - x: 0, - y: Tool.IMAGE_WIDTH, - width: Tool.IMAGE_WIDTH, - height: Tool.IMAGE_HEIGHT - }, - width: toolWidth, - height: toolHeight, - alpha: 0.9, - visible: true, - showButtonDown: true - }); +var browseExamplesButton = toolBar.addButton({ + imageURL: toolIconUrl + "examples-01.svg", + objectName: "examples", + yOffset: 50, + alpha: 0.9, +}); - toolBar.showTool(browseExamplesButton, true); - } +var browseExamplesButtonDown = false; - var browseExamplesButtonDown = false; - that.mousePressEvent = function(event) { - var clickedOverlay, - url, - file; +browseExamplesButton.clicked.connect(function(){ + toggleExamples(); +}); - if (!event.isLeftButton) { - // if another mouse button than left is pressed ignore it - return false; - } - - clickedOverlay = Overlays.getOverlayAtPoint({ - x: event.x, - y: event.y - }); - - if (browseExamplesButton === toolBar.clicked(clickedOverlay)) { - toggleExamples(); - return true; - } - - return false; - }; - - that.mouseReleaseEvent = function(event) { - var handled = false; - - - if (browseExamplesButtonDown) { - var clickedOverlay = Overlays.getOverlayAtPoint({ - x: event.x, - y: event.y - }); - } - - newModelButtonDown = false; - browseExamplesButtonDown = false; - - return handled; - } - - that.cleanup = function() { - toolBar.cleanup(); - }; - - initialize(); - return that; -}()); - -Controller.mousePressEvent.connect(toolBar.mousePressEvent) -Script.scriptEnding.connect(toolBar.cleanup); +Script.scriptEnding.connect(function () { + browseExamplesButton.clicked.disconnect(); +}); diff --git a/scripts/system/goto.js b/scripts/system/goto.js index 00b5e912c0..a2ade02a78 100644 --- a/scripts/system/goto.js +++ b/scripts/system/goto.js @@ -9,39 +9,21 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -Script.include("libraries/toolBars.js"); +var toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system"); -function initialPosition(windowDimensions, toolbar) { - return { - x: (windowDimensions.x / 2) - (Tool.IMAGE_WIDTH * 1), - y: windowDimensions.y - }; -} -var toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL, "highfidelity.goto.toolbar", initialPosition, { - x: -Tool.IMAGE_WIDTH / 2, - y: -Tool.IMAGE_HEIGHT -}); -var button = toolBar.addTool({ + +var button = toolBar.addButton({ + objectName: "goto", imageURL: Script.resolvePath("assets/images/tools/directory-01.svg"), - subImage: { - x: 0, - y: Tool.IMAGE_WIDTH, - width: Tool.IMAGE_WIDTH, - height: Tool.IMAGE_HEIGHT - }, - width: Tool.IMAGE_WIDTH, - height: Tool.IMAGE_HEIGHT, - alpha: 0.9, visible: true, - showButtonDown: true + yOffset: 50, + alpha: 0.9, }); -function onMousePress (event) { - if (event.isLeftButton && button === toolBar.clicked(Overlays.getOverlayAtPoint(event))) { - DialogsManager.toggleAddressBar(); - } -}; -Controller.mousePressEvent.connect(onMousePress) + +button.clicked.connect(function(){ + DialogsManager.toggleAddressBar(); +}); + Script.scriptEnding.connect(function () { - Controller.mousePressEvent.disconnect(onMousePress) - toolBar.cleanup(); + button.clicked.disconnect(); }); diff --git a/scripts/system/hmd.js b/scripts/system/hmd.js index 8b91e45676..2965c0d254 100644 --- a/scripts/system/hmd.js +++ b/scripts/system/hmd.js @@ -9,8 +9,6 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -Script.include("libraries/toolBars.js"); - var headset; // The preferred headset. Default to the first one found in the following list. var displayMenuName = "Display"; var desktopMenuItemName = "Desktop"; @@ -20,38 +18,25 @@ var desktopMenuItemName = "Desktop"; } }); -function initialPosition(windowDimensions, toolbar) { - return { - x: (windowDimensions.x / 2) - (Tool.IMAGE_WIDTH * 2.5), - y: windowDimensions.y - }; -} -var toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL, "highfidelity.hmd.toolbar", initialPosition, { - x: -Tool.IMAGE_WIDTH / 2, - y: -Tool.IMAGE_HEIGHT -}); -var button = toolBar.addTool({ - imageURL: Script.resolvePath("assets/images/tools/hmd-switch-01.svg"), - subImage: { - x: 0, - y: Tool.IMAGE_WIDTH, - width: Tool.IMAGE_WIDTH, - height: Tool.IMAGE_HEIGHT - }, - width: Tool.IMAGE_WIDTH, - height: Tool.IMAGE_HEIGHT, - alpha: 0.9, - visible: true, - showButtonDown: true -}); -function onMousePress (event) { - if (event.isLeftButton && button === toolBar.clicked(Overlays.getOverlayAtPoint(event))) { +var toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system"); +var button; + +if (headset) { + button = toolBar.addButton({ + objectName: "hmdToggle", + imageURL: Script.resolvePath("assets/images/tools/hmd-switch-01.svg"), + visible: true, + yOffset: 50, + alpha: 0.9, + }); + + button.clicked.connect(function(){ var isDesktop = Menu.isOptionChecked(desktopMenuItemName); Menu.setIsOptionChecked(isDesktop ? headset : desktopMenuItemName, true); - } -}; -Controller.mousePressEvent.connect(onMousePress) -Script.scriptEnding.connect(function () { - Controller.mousePressEvent.disconnect(onMousePress) - toolBar.cleanup(); -}); + }); + + Script.scriptEnding.connect(function () { + button.clicked.disconnect(); + }); +} + diff --git a/scripts/system/mute.js b/scripts/system/mute.js new file mode 100644 index 0000000000..f66b6852ea --- /dev/null +++ b/scripts/system/mute.js @@ -0,0 +1,29 @@ +// +// goto.js +// scripts/system/ +// +// Created by Howard Stearns on 2 Jun 2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system"); + + +var button = toolBar.addButton({ + objectName: "mute", + imageURL: Script.resolvePath("assets/images/tools/microphone.svg"), + visible: true, + alpha: 0.9, +}); + +button.clicked.connect(function(){ + var menuItem = "Mute Microphone"; + Menu.setIsOptionChecked(menuItem, !Menu.isOptionChecked(menuItem)); +}); + +Script.scriptEnding.connect(function () { + button.clicked.disconnect(); +}); diff --git a/tests/ui/qml/Stubs.qml b/tests/ui/qml/Stubs.qml index 8f828a0186..8c1465d54c 100644 --- a/tests/ui/qml/Stubs.qml +++ b/tests/ui/qml/Stubs.qml @@ -23,11 +23,23 @@ Item { function getUsername() { return "Jherico"; } } + Item { + objectName: "GL" + property string vendor: "" + } + Item { objectName: "ApplicationCompositor" property bool reticleOverDesktop: true } + Item { + objectName: "Controller" + function getRecommendedOverlayRect() { + return Qt.rect(0, 0, 1920, 1080); + } + } + Item { objectName: "Preferences" // List of categories obtained by logging categories as they are added in Interface in Preferences::addPreference(). diff --git a/tests/ui/qml/main.qml b/tests/ui/qml/main.qml index 33408fb821..19f6a55bfd 100644 --- a/tests/ui/qml/main.qml +++ b/tests/ui/qml/main.qml @@ -25,35 +25,67 @@ ApplicationWindow { property var tabs: []; property var urls: []; + property var toolbar; + property var lastButton; // Window visibility - - Button { - text: "restore all" - onClicked: { - for (var i = 0; i < desktop.windows.length; ++i) { - desktop.windows[i].shown = true - } - } - } - Button { - text: "toggle blue visible" - onClicked: { - blue.shown = !blue.shown - } - } - Button { - text: "toggle blue enabled" - onClicked: { - blue.enabled = !blue.enabled - } - } - Button { text: "toggle desktop" onClicked: desktop.togglePinned() } + Button { + text: "Create Toolbar" + onClicked: testButtons.toolbar = desktop.getToolbar("com.highfidelity.interface.toolbar.system"); + } + + Button { + text: "Toggle Toolbar Direction" + onClicked: testButtons.toolbar.horizontal = !testButtons.toolbar.horizontal + } + + Button { + readonly property var icons: [ + "edit-01.svg", + "model-01.svg", + "cube-01.svg", + "sphere-01.svg", + "light-01.svg", + "text-01.svg", + "web-01.svg", + "zone-01.svg", + "particle-01.svg", + ] + property int iconIndex: 0 + readonly property string toolIconUrl: "file:///C:/Users/bdavi/git/hifi/scripts/system/assets/images/tools/" + text: "Create Button" + onClicked: { + var name = icons[iconIndex]; + var url = toolIconUrl + name; + iconIndex = (iconIndex + 1) % icons.length; + var button = testButtons.lastButton = testButtons.toolbar.addButton({ + imageURL: url, + objectName: name, + subImage: { + y: 50, + }, + alpha: 0.9 + }); + + button.clicked.connect(function(){ + console.log("Clicked on button " + button.imageURL + " alpha " + button.alpha) + }); + } + } + + Button { + text: "Toggle Button Visible" + onClicked: testButtons.lastButton.visible = !testButtons.lastButton.visible + } + + + + // Error alerts /* Button { @@ -106,7 +138,7 @@ ApplicationWindow { */ // Browser - + /* Button { text: "Open Browser" onClicked: builder.createObject(desktop); @@ -114,8 +146,11 @@ ApplicationWindow { Browser {} } } + */ + // file dialog + /* Button { text: "Open Directory" @@ -153,7 +188,6 @@ ApplicationWindow { }) } } - /* */ // tabs @@ -306,6 +340,7 @@ ApplicationWindow { } */ + /* Window { id: blue closable: true @@ -350,6 +385,8 @@ ApplicationWindow { height: green.height; } } + */ + /* Window { id: yellow @@ -379,3 +416,7 @@ ApplicationWindow { } } } + + + + diff --git a/tests/ui/qmlscratch.pro b/tests/ui/qmlscratch.pro index 95be6a480e..151893de2f 100644 --- a/tests/ui/qmlscratch.pro +++ b/tests/ui/qmlscratch.pro @@ -29,6 +29,7 @@ DISTFILES += \ ../../interface/resources/qml/styles-uit/*.qml \ ../../interface/resources/qml/windows/*.qml \ ../../interface/resources/qml/hifi/*.qml \ + ../../interface/resources/qml/hifi/toolbars/*.qml \ ../../interface/resources/qml/hifi/dialogs/*.qml \ ../../interface/resources/qml/hifi/dialogs/preferences/*.qml \ ../../interface/resources/qml/hifi/overlays/*.qml diff --git a/tests/ui/src/main.cpp b/tests/ui/src/main.cpp index 0cabfe28f5..e3cf37ba04 100644 --- a/tests/ui/src/main.cpp +++ b/tests/ui/src/main.cpp @@ -86,9 +86,11 @@ int main(int argc, char *argv[]) { setChild(engine, "offscreenFlags"); setChild(engine, "Account"); setChild(engine, "ApplicationCompositor"); + setChild(engine, "Controller"); setChild(engine, "Desktop"); setChild(engine, "ScriptDiscoveryService"); setChild(engine, "HMD"); + setChild(engine, "GL"); setChild(engine, "MenuHelper"); setChild(engine, "Preferences"); setChild(engine, "urlHandler"); @@ -101,3 +103,4 @@ int main(int argc, char *argv[]) { } #include "main.moc" + From 6196c657f8e5fe8263bcd9bce09b4a4f0c860985 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Sat, 18 Jun 2016 14:17:47 -0700 Subject: [PATCH 69/93] Minimize triggering distance to HUD. --- interface/src/ui/OverlayConductor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index 74e27122e4..003de23030 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -32,7 +32,7 @@ bool OverlayConductor::headOutsideOverlay() const { glm::vec3 uiPos = uiTransform.getTranslation(); glm::vec3 uiForward = uiTransform.getRotation() * glm::vec3(0.0f, 0.0f, -1.0f); - const float MAX_COMPOSITOR_DISTANCE = 0.6f; + const float MAX_COMPOSITOR_DISTANCE = 0.99f; // If you're 1m from center of ui sphere, you're at the surface. const float MAX_COMPOSITOR_ANGLE = 180.0f; // rotation check is effectively disabled if (glm::distance(uiPos, hmdPos) > MAX_COMPOSITOR_DISTANCE || glm::dot(uiForward, hmdForward) < cosf(glm::radians(MAX_COMPOSITOR_ANGLE))) { From 81300ec1270f1d11eda3d5a530be5d59a4ad3041 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Sat, 18 Jun 2016 14:35:37 -0700 Subject: [PATCH 70/93] turn hud reset behavior on by default --- interface/src/avatar/MyAvatar.cpp | 4 ++-- interface/src/avatar/MyAvatar.h | 6 +++--- interface/src/ui/OverlayConductor.cpp | 2 +- interface/src/ui/PreferencesDialog.cpp | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 5d1efca902..6a69ee9a9a 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -722,7 +722,7 @@ void MyAvatar::saveData() { settings.setValue("displayName", _displayName); settings.setValue("collisionSoundURL", _collisionSoundURL); settings.setValue("useSnapTurn", _useSnapTurn); - settings.setValue("clearOverlayWhenDriving", _clearOverlayWhenDriving); + settings.setValue("clearOverlayWhenMoving", _clearOverlayWhenMoving); settings.endGroup(); } @@ -842,7 +842,7 @@ void MyAvatar::loadData() { setDisplayName(settings.value("displayName").toString()); setCollisionSoundURL(settings.value("collisionSoundURL", DEFAULT_AVATAR_COLLISION_SOUND_URL).toString()); setSnapTurn(settings.value("useSnapTurn", _useSnapTurn).toBool()); - setClearOverlayWhenDriving(settings.value("clearOverlayWhenDriving", _clearOverlayWhenDriving).toBool()); + setClearOverlayWhenMoving(settings.value("clearOverlayWhenMoving", _clearOverlayWhenMoving).toBool()); settings.endGroup(); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 6fa6e1fd19..96fa999de5 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -159,8 +159,8 @@ public: Q_INVOKABLE bool getSnapTurn() const { return _useSnapTurn; } Q_INVOKABLE void setSnapTurn(bool on) { _useSnapTurn = on; } - Q_INVOKABLE bool getClearOverlayWhenDriving() const { return _clearOverlayWhenDriving; } - Q_INVOKABLE void setClearOverlayWhenDriving(bool on) { _clearOverlayWhenDriving = on; } + Q_INVOKABLE bool getClearOverlayWhenMoving() const { return _clearOverlayWhenMoving; } + Q_INVOKABLE void setClearOverlayWhenMoving(bool on) { _clearOverlayWhenMoving = on; } Q_INVOKABLE void setHMDLeanRecenterEnabled(bool value) { _hmdLeanRecenterEnabled = value; } Q_INVOKABLE bool getHMDLeanRecenterEnabled() const { return _hmdLeanRecenterEnabled; } @@ -405,7 +405,7 @@ private: QString _fullAvatarModelName; QUrl _animGraphUrl {""}; bool _useSnapTurn { true }; - bool _clearOverlayWhenDriving { false }; + bool _clearOverlayWhenMoving { true }; // cache of the current HMD sensor position and orientation // in sensor space. diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index 74e27122e4..91046c4cc6 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -124,7 +124,7 @@ void OverlayConductor::update(float dt) { _flags &= ~SuppressedByDrive; } } else { - if (myAvatar->getClearOverlayWhenDriving() && drivingChanged && isDriving) { + if (myAvatar->getClearOverlayWhenMoving() && drivingChanged && isDriving) { _flags |= SuppressedByDrive; } } diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 6decef3240..c1705da206 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -62,9 +62,9 @@ void setupPreferences() { preferences->addPreference(new CheckPreference(AVATAR_BASICS, "Snap turn when in HMD", getter, setter)); } { - auto getter = [=]()->bool {return myAvatar->getClearOverlayWhenDriving(); }; - auto setter = [=](bool value) { myAvatar->setClearOverlayWhenDriving(value); }; - preferences->addPreference(new CheckPreference(AVATAR_BASICS, "Clear overlays when driving", getter, setter)); + auto getter = [=]()->bool {return myAvatar->getClearOverlayWhenMoving(); }; + auto setter = [=](bool value) { myAvatar->setClearOverlayWhenMoving(value); }; + preferences->addPreference(new CheckPreference(AVATAR_BASICS, "Clear overlays when moving", getter, setter)); } { auto getter = []()->QString { return Snapshot::snapshotsLocation.get(); }; From 17591323777d46f350221e8d34b6bc6d831ca054 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 14 Jun 2016 18:01:10 -0700 Subject: [PATCH 71/93] Use trigger to both grab and equip objects * Removed entry into HOLD state via secondary aka grip buttons. * Changed equip logic to prioritize equip over near over far grabs. * Added drop gesture, upside down controller + slight shake + trigger press * Bug fix for near grab snapping to an incorrect offset after a far grab (this bug is present in master) --- .../system/controllers/handControllerGrab.js | 743 +++++++++++------- 1 file changed, 466 insertions(+), 277 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 4ec1675ede..8c5d48667d 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -14,12 +14,11 @@ Script.include("/~/system/libraries/utils.js"); - // // add lines where the hand ray picking is happening // var WANT_DEBUG = false; -var WANT_DEBUG_STATE = false; +var WANT_DEBUG_STATE = true; var WANT_DEBUG_SEARCH_NAME = null; // @@ -126,8 +125,6 @@ var DEFAULT_GRABBABLE_DATA = { disableReleaseVelocity: false }; - - // sometimes we want to exclude objects from being picked var USE_BLACKLIST = true; var blacklist = []; @@ -140,19 +137,20 @@ var USE_ENTITY_LINES_FOR_MOVING = false; var USE_OVERLAY_LINES_FOR_MOVING = false; var USE_PARTICLE_BEAM_FOR_MOVING = true; - var USE_SPOTLIGHT = false; var USE_POINTLIGHT = false; +var FORBIDDEN_GRAB_NAMES = ["Grab Debug Entity", "grab pointer"]; +var FORBIDDEN_GRAB_TYPES = ['Unknown', 'Light', 'PolyLine', 'Zone']; + // states for the state machine var STATE_OFF = 0; var STATE_SEARCHING = 1; -var STATE_HOLD_SEARCHING = 2; -var STATE_DISTANCE_HOLDING = 3; -var STATE_NEAR_GRABBING = 4; -var STATE_NEAR_TRIGGER = 5; -var STATE_FAR_TRIGGER = 6; -var STATE_HOLD = 7; +var STATE_DISTANCE_HOLDING = 2; +var STATE_NEAR_GRABBING = 3; +var STATE_NEAR_TRIGGER = 4; +var STATE_FAR_TRIGGER = 5; +var STATE_HOLD = 6; // "collidesWith" is specified by comma-separated list of group names // the possible group names are: static, dynamic, kinematic, myAvatar, otherAvatar @@ -175,10 +173,6 @@ CONTROLLER_STATE_MACHINE[STATE_SEARCHING] = { enterMethod: "searchEnter", exitMethod: "searchExit" }; -CONTROLLER_STATE_MACHINE[STATE_HOLD_SEARCHING] = { - name: "hold_searching", - updateMethod: "search" -}; CONTROLLER_STATE_MACHINE[STATE_DISTANCE_HOLDING] = { name: "distance_holding", enterMethod: "distanceHoldingEnter", @@ -259,6 +253,58 @@ function restore2DMode() { } } +function filter(array, predicate) { + var i, l = array.length; + var result = []; + for (i = 0; i < l; i++) { + if (predicate(array[i])) { + result.push(array[i]); + } + } + return result; +} + +// constructor +function EntityPropertiesCache() { + this.cache = {}; +} +EntityPropertiesCache.prototype.clear = function() { + this.cache = {}; +}; +EntityPropertiesCache.prototype.findEntities = function(position, radius) { + var entities = Entities.findEntities(position, radius); + var i, l = entities.length; + for (i = 0; i < l; i++) { + this.addEntity(entities[i]); + } +}; +EntityPropertiesCache.prototype.addEntity = function(entityID) { + var props = Entities.getEntityProperties(entityID, GRABBABLE_PROPERTIES); + var grabbableProps = getEntityCustomData(GRABBABLE_DATA_KEY, entityID, DEFAULT_GRABBABLE_DATA); + var grabProps = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {}); + var wearableProps = getEntityCustomData("wearable", entityID, {}); + this.cache[entityID] = { props: props, grabbableProps: grabbableProps, grabProps: grabProps, wearableProps: wearableProps }; +}; +EntityPropertiesCache.prototype.getEntities = function() { + return Object.keys(this.cache); +} +EntityPropertiesCache.prototype.getProps = function(entityID) { + var obj = this.cache[entityID] + return obj ? obj.props : undefined; +}; +EntityPropertiesCache.prototype.getGrabbableProps = function(entityID) { + var obj = this.cache[entityID] + return obj ? obj.grabbableProps : undefined; +}; +EntityPropertiesCache.prototype.getGrabProps = function(entityID) { + var obj = this.cache[entityID] + return obj ? obj.grabProps : undefined; +}; +EntityPropertiesCache.prototype.getWearableProps = function(entityID) { + var obj = this.cache[entityID] + return obj ? obj.wearableProps : undefined; +}; + function MyController(hand) { this.hand = hand; if (this.hand === RIGHT_HAND) { @@ -295,6 +341,8 @@ function MyController(hand) { this.overlayLine = null; this.searchSphere = null; + this.waitForTriggerRelease = false; + // how far from camera to search intersection? var DEFAULT_SEARCH_SPHERE_DISTANCE = 1000; this.intersectionDistance = 0.0; @@ -307,16 +355,18 @@ function MyController(hand) { this.lastPickTime = 0; this.lastUnequipCheckTime = 0; + this.entityPropertyCache = new EntityPropertiesCache(); + var _this = this; - var suppressedIn2D = [STATE_OFF, STATE_SEARCHING, STATE_HOLD_SEARCHING]; + var suppressedIn2D = [STATE_OFF, STATE_SEARCHING]; this.ignoreInput = function () { // We've made the decision to use 'this' for new code, even though it is fragile, // in order to keep/ the code uniform without making any no-op line changes. return (-1 !== suppressedIn2D.indexOf(this.state)) && isIn2DMode(); }; - this.update = function() { + this.update = function(deltaTime) { this.updateSmoothedTrigger(); @@ -329,7 +379,7 @@ function MyController(hand) { var updateMethodName = CONTROLLER_STATE_MACHINE[this.state].updateMethod; var updateMethod = this[updateMethodName]; if (updateMethod) { - updateMethod.call(this); + updateMethod.call(this, deltaTime); } else { print("WARNING: could not find updateMethod for state " + stateToName(this.state)); } @@ -343,12 +393,12 @@ function MyController(hand) { Entities.callEntityMethod(this.grabbedEntity, entityMethodName, args); } - this.setState = function(newState) { + this.setState = function(newState, reason) { this.grabSphereOff(); if (WANT_DEBUG || WANT_DEBUG_STATE) { var oldStateName = stateToName(this.state); var newStateName = stateToName(newState); - print("STATE (" + this.hand + "): " + newStateName + " <-- " + oldStateName); + print("STATE (" + this.hand + "): " + newStateName + " <-- " + oldStateName + ", reason = " + reason); } // exit the old state @@ -819,14 +869,16 @@ function MyController(hand) { }; this.off = function() { - if (this.triggerSmoothedSqueezed() || this.secondarySqueezed()) { + + if (this.triggerSmoothedReleased()) { + this.waitForTriggerRelease = false; + } + if (!this.waitForTriggerRelease && this.triggerSmoothedSqueezed()) { this.lastPickTime = 0; var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; this.startingHandRotation = Controller.getPoseValue(controllerHandInput).rotation; if (this.triggerSmoothedSqueezed()) { - this.setState(STATE_SEARCHING); - } else if (this.secondarySqueezed()) { - this.setState(STATE_HOLD_SEARCHING); + this.setState(STATE_SEARCHING, "trigger squeeze detected"); } } }; @@ -875,265 +927,345 @@ function MyController(hand) { this.equipHotspotOverlays = []; }; + /** + * Performs ray pick test from the hand controller into the world + * @param {number} which hand to use, RIGHT_HAND or LEFT_HAND + * @returns {object} returns object with two keys entityID and distance + */ + this.calcRayPickInfo = function(hand) { + + var pose = Controller.getPoseValue((hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand); + var worldHandPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, pose.translation), MyAvatar.position); + var worldHandRotation = Quat.multiply(MyAvatar.orientation, pose.rotation); + + var pickRay = { + origin: PICK_WITH_HAND_RAY ? worldHandPosition : Camera.position, + direction: PICK_WITH_HAND_RAY ? Quat.getUp(worldHandRotation) : Vec3.mix(Quat.getUp(worldHandRotation), + Quat.getFront(Camera.orientation), + HAND_HEAD_MIX_RATIO), + length: PICK_MAX_DISTANCE + }; + + var result = { entityID: null, + searchRay: pickRay, + distance: PICK_MAX_DISTANCE }; + + var now = Date.now(); + if (now - this.lastPickTime < MSECS_PER_SEC / PICKS_PER_SECOND_PER_HAND) { + return result; + } + this.lastPickTime = now; + + var directionNormalized = Vec3.normalize(pickRay.direction); + var directionBacked = Vec3.multiply(directionNormalized, PICK_BACKOFF_DISTANCE); + var pickRayBacked = { + origin: Vec3.subtract(pickRay.origin, directionBacked), + direction: pickRay.direction + }; + + var intersection; + if (USE_BLACKLIST === true && blacklist.length !== 0) { + intersection = Entities.findRayIntersection(pickRayBacked, true, [], blacklist); + } else { + intersection = Entities.findRayIntersection(pickRayBacked, true); + } + + var overlayIntersection = Overlays.findRayIntersection(pickRayBacked); + if (!intersection.intersects || (overlayIntersection.intersects && (intersection.distance > overlayIntersection.distance))) { + intersection = overlayIntersection; + } + + if (intersection.intersects) { + return { entityID: intersection.entityID, + searchRay: pickRay, + distance: Vec3.distance(pickRay.origin, intersection.intersection) } + } else { + return result; + } + }; + + this.entityWantsTrigger = function (entityID) { + var grabbableProps = this.entityPropertyCache.getGrabbableProps(entityID); + return grabbableProps && grabbableProps.wantsTrigger; + }; + + this.entityIsEquippable = function (entityID, handPosition) { + var props = this.entityPropertyCache.getProps(entityID); + var distance = Vec3.distance(props.position, handPosition); + var grabProps = this.entityPropertyCache.getGrabProps(entityID); + var debug = true;//(WANT_DEBUG_SEARCH_NAME && props.name === WANT_DEBUG_SEARCH_NAME); + + var refCount = ("refCount" in grabProps) ? grabProps.refCount : 0; + if (refCount > 0) { + if (debug) { + print("equip is skipping '" + props.name + "': it is already grabbed"); + } + return false; + } + + if (distance > NEAR_PICK_MAX_DISTANCE) { + if (debug) { + print("equip is skipping '" + props.name + "': too far away."); + } + return false; + } + + var wearableProps = this.entityPropertyCache.getWearableProps(entityID); + if (!wearableProps || !wearableProps.joints) { + if (debug) { + print("equip is skipping '" + props.name + "': no wearable attach-point"); + } + return false; + } + + var handJointName = this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"; + if (!wearableProps.joints[handJointName]) { + if (debug) { + print("equip is skipping '" + props.name + "': no wearable joint for " + handJointName); + } + return false; + } + + return true; + }; + + this.entityIsGrabbable = function (entityID) { + var grabbableProps = this.entityPropertyCache.getGrabbableProps(entityID); + var grabProps = this.entityPropertyCache.getGrabProps(entityID); + var props = this.entityPropertyCache.getProps(entityID); + var physical = propsArePhysical(props); + var grabbable = false; + var debug = (WANT_DEBUG_SEARCH_NAME && props.name === WANT_DEBUG_SEARCH_NAME); + + if (physical) { + // physical things default to grabbable + grabbable = true; + } else { + // non-physical things default to non-grabbable unless they are already grabbed + if ("refCount" in grabProps && grabProps.refCount > 0) { + grabbable = true; + } else { + grabbable = false; + } + } + + if (grabbableProps.hasOwnProperty("grabbable")) { + grabbable = grabbableProps.grabbable; + } + + if (!grabbable && !grabbableProps.wantsTrigger) { + if (debug) { + print("grab is skipping '" + props.name + "': not grabbable."); + } + return false; + } + if (FORBIDDEN_GRAB_TYPES.indexOf(props.type) >= 0) { + if (debug) { + print("grab is skipping '" + props.name + "': forbidden entity type."); + } + return false; + } + if (props.locked && !grabbableProps.wantsTrigger) { + if (debug) { + print("grab is skipping '" + props.name + "': locked and not triggerable."); + } + return false; + } + if (FORBIDDEN_GRAB_NAMES.indexOf(props.name) >= 0) { + if (debug) { + print("grab is skipping '" + props.name + "': forbidden name."); + } + return false; + } + + return true; + }; + + this.entityIsDistanceGrabbable = function(entityID, handPosition) { + if (!this.entityIsGrabbable(entityID)) { + return false; + } + + var props = this.entityPropertyCache.getProps(entityID); + var distance = Vec3.distance(props.position, handPosition); + var debug = (WANT_DEBUG_SEARCH_NAME && props.name === WANT_DEBUG_SEARCH_NAME); + + // we can't distance-grab non-physical + var isPhysical = propsArePhysical(props); + if (!isPhysical) { + if (debug) { + print("distance grab is skipping '" + props.name + "': not physical"); + } + return false; + } + + if (distance > PICK_MAX_DISTANCE) { + // too far away, don't grab + if (debug) { + print("distance grab is skipping '" + props.name + "': too far away."); + } + return false; + } + + if (entityIsGrabbedByOther(entityID)) { + // don't distance grab something that is already grabbed. + if (debug) { + print("distance grab is skipping '" + props.name + "': already grabbed by another."); + } + return false; + } + + return true; + }; + + this.entityIsNearGrabbable = function(entityID, handPosition) { + + if (!this.entityIsGrabbable(entityID)) { + return false; + } + + var props = this.entityPropertyCache.getProps(entityID); + var distance = Vec3.distance(props.position, handPosition); + var debug = (WANT_DEBUG_SEARCH_NAME && props.name === WANT_DEBUG_SEARCH_NAME); + + if (distance > NEAR_PICK_MAX_DISTANCE) { + // too far away, don't grab + if (debug) { + print(" grab is skipping '" + props.name + "': too far away."); + } + return false; + } + + return true; + }; + this.search = function() { + var _this = this; + var name; + this.grabbedEntity = null; this.isInitialGrab = false; this.shouldResetParentOnRelease = false; this.checkForStrayChildren(); - if (this.state == STATE_SEARCHING && this.triggerSmoothedReleased()) { - this.setState(STATE_OFF); + if (this.triggerSmoothedReleased()) { + this.setState(STATE_OFF, "trigger released"); return; } - if (this.state == STATE_HOLD_SEARCHING && this.secondaryReleased()) { - this.setState(STATE_OFF); - return; - } - - // the trigger is being pressed, so do a ray test to see what we are hitting var handPosition = this.getHandPosition(); if (SHOW_GRAB_SPHERE) { this.grabSphereOn(); } - var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; - var currentControllerPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, - Controller.getPoseValue(controllerHandInput).translation), - MyAvatar.position); - var avatarControllerPose = Controller.getPoseValue((this.hand === RIGHT_HAND) ? - Controller.Standard.RightHand : Controller.Standard.LeftHand); - var controllerRotation = Quat.multiply(MyAvatar.orientation, avatarControllerPose.rotation); + this.entityPropertyCache.clear(); + this.entityPropertyCache.findEntities(handPosition, GRAB_RADIUS); + var candidateEntities = this.entityPropertyCache.getEntities(); - var distantPickRay = { - origin: PICK_WITH_HAND_RAY ? currentControllerPosition : Camera.position, - direction: PICK_WITH_HAND_RAY ? Quat.getUp(controllerRotation) : Vec3.mix(Quat.getUp(controllerRotation), - Quat.getFront(Camera.orientation), - HAND_HEAD_MIX_RATIO), - length: PICK_MAX_DISTANCE - }; + var equippableEntities = filter(candidateEntities, function (entity) { + return _this.entityIsEquippable(entity, handPosition); + }); - // Pick at some maximum rate, not always - var pickRays = []; - var now = Date.now(); - if (now - this.lastPickTime > MSECS_PER_SEC / PICKS_PER_SECOND_PER_HAND) { - pickRays = [distantPickRay]; - this.lastPickTime = now; - } - - var rayPickedCandidateEntities = []; // the list of candidates to consider grabbing - - this.intersectionDistance = 0.0; - for (var index = 0; index < pickRays.length; ++index) { - var pickRay = pickRays[index]; - var directionNormalized = Vec3.normalize(pickRay.direction); - var directionBacked = Vec3.multiply(directionNormalized, PICK_BACKOFF_DISTANCE); - var pickRayBacked = { - origin: Vec3.subtract(pickRay.origin, directionBacked), - direction: pickRay.direction - }; - - var intersection; - - if (USE_BLACKLIST === true && blacklist.length !== 0) { - intersection = Entities.findRayIntersection(pickRayBacked, true, [], blacklist); - } else { - intersection = Entities.findRayIntersection(pickRayBacked, true); - } - var overlayIntersection = Overlays.findRayIntersection(pickRayBacked); - if (!intersection.intersects || (overlayIntersection.intersects && (intersection.distance > overlayIntersection.distance))) { - intersection = overlayIntersection; - } - // If we want to share results with other scripts, this is where we would do it. - - if (intersection.intersects) { - if (intersection.entityID) { - rayPickedCandidateEntities.push(intersection.entityID); - } - this.intersectionDistance = Vec3.distance(pickRay.origin, intersection.intersection); - } - } - - var nearPickedCandidateEntities = Entities.findEntities(handPosition, GRAB_RADIUS); - var candidateEntities = rayPickedCandidateEntities.concat(nearPickedCandidateEntities); - - var forbiddenNames = ["Grab Debug Entity", "grab pointer"]; - var forbiddenTypes = ['Unknown', 'Light', 'PolyLine', 'Zone']; - - var minDistance = PICK_MAX_DISTANCE; - var i, props, distance, grabbableData; - this.grabbedEntity = null; - for (i = 0; i < candidateEntities.length; i++) { - var grabbableDataForCandidate = - getEntityCustomData(GRABBABLE_DATA_KEY, candidateEntities[i], DEFAULT_GRABBABLE_DATA); - var grabDataForCandidate = getEntityCustomData(GRAB_USER_DATA_KEY, candidateEntities[i], {}); - var propsForCandidate = Entities.getEntityProperties(candidateEntities[i], GRABBABLE_PROPERTIES); - var near = (nearPickedCandidateEntities.indexOf(candidateEntities[i]) >= 0); - - var physical = propsArePhysical(propsForCandidate); - var grabbable; - if (physical) { - // physical things default to grabbable - grabbable = true; - } else { - // non-physical things default to non-grabbable unless they are already grabbed - if ("refCount" in grabDataForCandidate && grabDataForCandidate.refCount > 0) { - grabbable = true; - } else { - grabbable = false; - } - } - - if ("grabbable" in grabbableDataForCandidate) { - // if userData indicates that this is grabbable or not, override the default. - grabbable = grabbableDataForCandidate.grabbable; - } - - if (!grabbable && !grabbableDataForCandidate.wantsTrigger) { - if (WANT_DEBUG_SEARCH_NAME && propsForCandidate.name == WANT_DEBUG_SEARCH_NAME) { - print("grab is skipping '" + WANT_DEBUG_SEARCH_NAME + "': not grabbable."); - } - continue; - } - if (forbiddenTypes.indexOf(propsForCandidate.type) >= 0) { - if (WANT_DEBUG_SEARCH_NAME && propsForCandidate.name == WANT_DEBUG_SEARCH_NAME) { - print("grab is skipping '" + WANT_DEBUG_SEARCH_NAME + "': forbidden entity type."); - } - continue; - } - if (propsForCandidate.locked && !grabbableDataForCandidate.wantsTrigger) { - if (WANT_DEBUG_SEARCH_NAME && propsForCandidate.name == WANT_DEBUG_SEARCH_NAME) { - print("grab is skipping '" + WANT_DEBUG_SEARCH_NAME + "': locked and not triggerable."); - } - continue; - } - if (forbiddenNames.indexOf(propsForCandidate.name) >= 0) { - if (WANT_DEBUG_SEARCH_NAME && propsForCandidate.name == WANT_DEBUG_SEARCH_NAME) { - print("grab is skipping '" + WANT_DEBUG_SEARCH_NAME + "': forbidden name."); - } - continue; - } - - distance = Vec3.distance(propsForCandidate.position, handPosition); - if (distance > PICK_MAX_DISTANCE) { - // too far away, don't grab - if (WANT_DEBUG_SEARCH_NAME && propsForCandidate.name == WANT_DEBUG_SEARCH_NAME) { - print("grab is skipping '" + WANT_DEBUG_SEARCH_NAME + "': too far away."); - } - continue; - } - if (propsForCandidate.parentID != NULL_UUID && this.state == STATE_HOLD_SEARCHING) { - // don't allow a double-equip - if (WANT_DEBUG_SEARCH_NAME && propsForCandidate.name == WANT_DEBUG_SEARCH_NAME) { - print("grab is skipping '" + WANT_DEBUG_SEARCH_NAME + "': it's a child"); - } - continue; - } - - if (this.state == STATE_SEARCHING && - !physical && distance > NEAR_PICK_MAX_DISTANCE && !near && !grabbableDataForCandidate.wantsTrigger) { - // we can't distance-grab non-physical - if (WANT_DEBUG_SEARCH_NAME && propsForCandidate.name == WANT_DEBUG_SEARCH_NAME) { - print("grab is skipping '" + WANT_DEBUG_SEARCH_NAME + "': not physical and too far for near-grab"); - } - continue; - } - - if (distance < minDistance) { - this.grabbedEntity = candidateEntities[i]; - minDistance = distance; - props = propsForCandidate; - grabbableData = grabbableDataForCandidate; - } - } - if ((this.grabbedEntity !== null) && (this.triggerSmoothedGrab() || this.secondarySqueezed())) { - // We are squeezing enough to grab, and we've found an entity that we'll try to do something with. - var isNear = (nearPickedCandidateEntities.indexOf(this.grabbedEntity) >= 0) || minDistance <= NEAR_PICK_MAX_DISTANCE; - var isPhysical = propsArePhysical(props); - - // near or far trigger - if (grabbableData.wantsTrigger) { - this.setState(isNear ? STATE_NEAR_TRIGGER : STATE_FAR_TRIGGER); + var entity; + if (equippableEntities.length > 0) { + // sort by distance + equippableEntities.sort(function (a, b) { + var aDistance = Vec3.distance(_this.entityPropertyCache.getProps(a).position, handPosition); + var bDistance = Vec3.distance(_this.entityPropertyCache.getProps(b).position, handPosition); + return aDistance - bDistance; + }); + entity = equippableEntities[0]; + if (this.triggerSmoothedGrab()) { + this.grabbedEntity = entity; + this.setState(STATE_HOLD, "eqipping '" + this.entityPropertyCache.getProps(entity).name + "'"); return; + } else { + // TODO: highlight the equippable object? } - // near grab with action or equip - var grabData = getEntityCustomData(GRAB_USER_DATA_KEY, this.grabbedEntity, {}); - var refCount = ("refCount" in grabData) ? grabData.refCount : 0; - if (isNear && (refCount < 1 || entityHasActions(this.grabbedEntity))) { - if (this.state == STATE_SEARCHING) { - this.setState(STATE_NEAR_GRABBING); - } else { // (this.state == STATE_HOLD_SEARCHING) - // if there was already an action, we'll need to set the parent back to null once we release - this.shouldResetParentOnRelease = true; - this.previousParentID = props.parentID; - this.previousParentJointIndex = props.parentJointIndex; - this.setState(STATE_HOLD); - } - return; - } - // far grab - if (isPhysical && !isNear) { - if (entityIsGrabbedByOther(this.grabbedEntity)) { - // don't distance grab something that is already grabbed. - if (WANT_DEBUG_SEARCH_NAME && props.name == WANT_DEBUG_SEARCH_NAME) { - print("grab is skipping '" + WANT_DEBUG_SEARCH_NAME + "': already grabbed by another."); - } + } + + var rayPickInfo = this.calcRayPickInfo(this.hand); + this.intersectionDistance = rayPickInfo.distance; + if (rayPickInfo.entityID) { + candidateEntities.push(rayPickInfo.entityID); + this.entityPropertyCache.addEntity(rayPickInfo.entityID); + } + + var grabbableEntities = filter(candidateEntities, function (entity) { + return _this.entityIsNearGrabbable(entity, handPosition); + }); + + if (grabbableEntities.length > 0) { + // sort by distance + grabbableEntities.sort(function (a, b) { + var aDistance = Vec3.distance(_this.entityPropertyCache.getProps(a).position, handPosition); + var bDistance = Vec3.distance(_this.entityPropertyCache.getProps(b).position, handPosition); + return aDistance - bDistance; + }); + entity = grabbableEntities[0]; + name = this.entityPropertyCache.getProps(entity).name; + this.grabbedEntity = entity; + if (this.entityWantsTrigger(entity)) { + if (this.triggerSmoothedGrab()) { + this.setState(STATE_NEAR_TRIGGER, "near trigger '" + name + "'"); return; + } else { + // TODO: highlight the near-triggerable object? } - this.temporaryPositionOffset = null; - if (!this.hasPresetOffsets()) { - // We want to give a temporary position offset to this object so it is pulled close to hand - var intersectionPointToCenterDistance = Vec3.length(Vec3.subtract(intersection.intersection, - intersection.properties.position)); - var handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"); - var handJointPosition = MyAvatar.getJointPosition(handJointIndex); - this.temporaryPositionOffset = - Vec3.normalize(Vec3.subtract(intersection.properties.position, handJointPosition)); - this.temporaryPositionOffset = Vec3.multiply(this.temporaryPositionOffset, - intersectionPointToCenterDistance * - FAR_TO_NEAR_GRAB_PADDING_FACTOR); - } - this.setState(STATE_DISTANCE_HOLDING); - - this.searchSphereOff(); - return; - } - - // else this thing isn't physical. grab it by reparenting it (but not if we've already - // grabbed it). - if (refCount < 1) { - if (this.state == STATE_SEARCHING) { - this.setState(STATE_NEAR_GRABBING); - } else { // this.state == STATE_HOLD_SEARCHING) - this.setState(STATE_HOLD); - } - return; } else { - // it's not physical and it's already held via parenting. go ahead and grab it, but - // save off the current parent and joint. this wont always be right if there are more than - // two grabs and the order of release isn't opposite of the order of grabs. - this.shouldResetParentOnRelease = true; - this.previousParentID = props.parentID; - this.previousParentJointIndex = props.parentJointIndex; - if (this.state == STATE_SEARCHING) { - this.setState(STATE_NEAR_GRABBING); - } else { // (this.state == STATE_HOLD_SEARCHING) - this.setState(STATE_HOLD); + if (this.triggerSmoothedGrab()) { + + var props = this.entityPropertyCache.getProps(entity); + var grabProps = this.entityPropertyCache.getGrabProps(entity); + var refCount = grabProps.refCount ? grabProps.refCount : 0; + if (refCount >= 1) { + // if another person is holding the object, remember to restore the + // parent info, when we are finished grabbing it. + this.shouldResetParentOnRelease = true; + this.previousParentID = props.parentID; + this.previousParentJointIndex = props.parentJointIndex; + } + + this.setState(STATE_NEAR_GRABBING, "near grab '" + name + "'"); + return; + } else { + // TODO: highlight the grabbable object? + } + } + return; + } + + if (rayPickInfo.entityID) { + entity = rayPickInfo.entityID; + name = this.entityPropertyCache.getProps(entity).name; + if (this.entityWantsTrigger(entity)) { + if (this.triggerSmoothedGrab()) { + this.grabbedEntity = entity; + this.setState(STATE_FAR_TRIGGER, "far trigger '" + name + "'"); + return; + } else { + // TODO: highlight the far-triggerable object? + } + } else if (this.entityIsDistanceGrabbable(rayPickInfo.entityID, handPosition)) { + if (this.triggerSmoothedGrab()) { + this.grabbedEntity = entity; + this.setState(STATE_DISTANCE_HOLDING, "distance hold '" + name + "'"); + return; + } else { + // TODO: highlight the far-grabbable object? } - return; } } //search line visualizations if (USE_ENTITY_LINES_FOR_SEARCHING === true) { - this.lineOn(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR); + this.lineOn(rayPickInfo.searchRay.origin, Vec3.multiply(rayPickInfo.searchRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR); } - this.searchIndicatorOn(distantPickRay); + this.searchIndicatorOn(rayPickInfo.searchRay); Reticle.setVisible(false); - }; this.distanceGrabTimescale = function(mass, distance) { @@ -1210,8 +1342,8 @@ function MyController(hand) { }; this.distanceHolding = function() { - if (this.triggerSmoothedReleased() && this.secondaryReleased()) { - this.setState(STATE_OFF); + if (this.triggerSmoothedReleased()) { + this.setState(STATE_OFF, "trigger released"); this.callEntityMethodOnGrabbed("releaseGrab"); return; } @@ -1230,7 +1362,7 @@ function MyController(hand) { var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); var now = Date.now(); - var deltaTime = (now - this.currentObjectTime) / MSECS_PER_SEC; // convert to seconds + var deltaObjectTime = (now - this.currentObjectTime) / MSECS_PER_SEC; // convert to seconds this.currentObjectTime = now; // the action was set up when this.distanceHolding was called. update the targets. @@ -1267,17 +1399,17 @@ function MyController(hand) { // Update radialVelocity var lastVelocity = Vec3.subtract(controllerPositionVSAvatar, this.previousControllerPositionVSAvatar); - lastVelocity = Vec3.multiply(lastVelocity, 1.0 / deltaTime); + lastVelocity = Vec3.multiply(lastVelocity, 1.0 / deltaObjectTime); var newRadialVelocity = Vec3.dot(lastVelocity, Vec3.normalize(Vec3.subtract(grabbedProperties.position, controllerPosition))); var VELOCITY_AVERAGING_TIME = 0.016; - this.grabRadialVelocity = (deltaTime / VELOCITY_AVERAGING_TIME) * newRadialVelocity + - (1.0 - (deltaTime / VELOCITY_AVERAGING_TIME)) * this.grabRadialVelocity; + this.grabRadialVelocity = (deltaObjectTime / VELOCITY_AVERAGING_TIME) * newRadialVelocity + + (1.0 - (deltaObjectTime / VELOCITY_AVERAGING_TIME)) * this.grabRadialVelocity; var RADIAL_GRAB_AMPLIFIER = 10.0; if (Math.abs(this.grabRadialVelocity) > 0.0) { - this.grabRadius = this.grabRadius + (this.grabRadialVelocity * deltaTime * this.grabRadius * RADIAL_GRAB_AMPLIFIER); + this.grabRadius = this.grabRadius + (this.grabRadialVelocity * deltaObjectTime * this.grabRadius * RADIAL_GRAB_AMPLIFIER); } var newTargetPosition = Vec3.multiply(this.grabRadius, Quat.getUp(controllerRotation)); @@ -1431,11 +1563,56 @@ function MyController(hand) { } } + this.dropGestureReset = function() { + this.fastHandMoveDetected = false; + this.fastHandMoveTimer = 0; + }; + + this.dropGestureProcess = function(deltaTime) { + var pose = Controller.getPoseValue((this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand); + var worldHandVelocity = Vec3.multiplyQbyV(MyAvatar.orientation, pose.velocity); + var worldHandRotation = Quat.multiply(MyAvatar.orientation, pose.rotation); + + if (this.fastHandMoveDetected) { + this.fastHandMoveTimer -= deltaTime; + } + if (this.fastHandMoveTimer < 0) { + this.fastHandMoveDetected = false; + } + var FAST_HAND_SPEED_REST_TIME = 1; // sec + var FAST_HAND_SPEED_THRESHOLD = 0.4; // m/sec + if (Vec3.length(worldHandVelocity) > FAST_HAND_SPEED_THRESHOLD) { + this.fastHandMoveDetected = true; + this.fastHandMoveTimer = FAST_HAND_SPEED_REST_TIME; + } + + var localHandUpAxis = this.hand === RIGHT_HAND ? {x: 1, y: 0, z: 0} : {x: -1, y: 0, z: 0}; + var worldHandUpAxis = Vec3.multiplyQbyV(worldHandRotation, localHandUpAxis); + var DOWN = {x: 0, y: -1, z: 0}; + var ROTATION_THRESHOLD = Math.cos(Math.PI / 8); + + var handIsUpsideDown = false; + if (Vec3.dot(worldHandUpAxis, DOWN) > ROTATION_THRESHOLD) { + handIsUpsideDown = true; + } + + var WANT_DEBUG = false; + if (WANT_DEBUG) { + print("zAxis = " + worldHandUpAxis.x + ", " + worldHandUpAxis.y + ", " + worldHandUpAxis.z); + print("dot = " + Vec3.dot(worldHandUpAxis, DOWN) + ", ROTATION_THRESHOLD = " + ROTATION_THRESHOLD); + print("handMove = " + this.fastHandMoveDetected + ", handIsUpsideDown = " + handIsUpsideDown); + } + + return this.fastHandMoveDetected && handIsUpsideDown; + }; + this.nearGrabbingEnter = function() { this.lineOff(); this.overlayLineOff(); + this.dropGestureReset(); + if (this.entityActivated) { var saveGrabbedID = this.grabbedEntity; this.release(); @@ -1524,14 +1701,18 @@ function MyController(hand) { this.currentAngularVelocity = ZERO_VEC; }; - this.nearGrabbing = function() { + this.nearGrabbing = function(deltaTime) { + + var dropDetected = this.dropGestureProcess(deltaTime); + if (this.state == STATE_NEAR_GRABBING && this.triggerSmoothedReleased()) { - this.setState(STATE_OFF); + this.setState(STATE_OFF, "trigger released"); this.callEntityMethodOnGrabbed("releaseGrab"); return; } - if (this.state == STATE_HOLD && this.secondaryReleased()) { - this.setState(STATE_OFF); + + if (this.state == STATE_HOLD && dropDetected && this.triggerSmoothedGrab()) { + this.setState(STATE_OFF, "drop detected"); this.callEntityMethodOnGrabbed("releaseEquip"); return; } @@ -1541,7 +1722,7 @@ function MyController(hand) { var props = Entities.getEntityProperties(this.grabbedEntity, ["localPosition", "parentID", "position", "rotation"]); if (!props.position) { // server may have reset, taking our equipped entity with it. move back to "off" stte - this.setState(STATE_OFF); + this.setState(STATE_OFF, "entity has no position property"); this.callEntityMethodOnGrabbed("releaseGrab"); return; } @@ -1561,7 +1742,7 @@ function MyController(hand) { // for whatever reason, the held/equipped entity has been pulled away. ungrab or unequip. print("handControllerGrab -- autoreleasing held or equipped item because it is far from hand." + props.parentID + " " + vec3toStr(props.position)); - this.setState(STATE_OFF); + this.setState(STATE_OFF, "held object too far away"); if (this.state == STATE_NEAR_GRABBING) { this.callEntityMethodOnGrabbed("releaseGrab"); } else { // this.state == STATE_HOLD @@ -1581,17 +1762,17 @@ function MyController(hand) { var handControllerPosition = (this.hand === RIGHT_HAND) ? MyAvatar.rightHandPosition : MyAvatar.leftHandPosition; - var deltaTime = (now - this.currentObjectTime) / MSECS_PER_SEC; // convert to seconds + var deltaObjectTime = (now - this.currentObjectTime) / MSECS_PER_SEC; // convert to seconds - if (deltaTime > 0.0) { + if (deltaObjectTime > 0.0) { var worldDeltaPosition = Vec3.subtract(props.position, this.currentObjectPosition); var previousEulers = Quat.safeEulerAngles(this.currentObjectRotation); var newEulers = Quat.safeEulerAngles(props.rotation); var worldDeltaRotation = Vec3.subtract(newEulers, previousEulers); - this.currentVelocity = Vec3.multiply(worldDeltaPosition, 1.0 / deltaTime); - this.currentAngularVelocity = Vec3.multiply(worldDeltaRotation, Math.PI / (deltaTime * 180.0)); + this.currentVelocity = Vec3.multiply(worldDeltaPosition, 1.0 / deltaObjectTime); + this.currentAngularVelocity = Vec3.multiply(worldDeltaRotation, Math.PI / (deltaObjectTime * 180.0)); this.currentObjectPosition = props.position; this.currentObjectRotation = props.rotation; @@ -1638,8 +1819,8 @@ function MyController(hand) { }; this.nearTrigger = function() { - if (this.triggerSmoothedReleased() && this.secondaryReleased()) { - this.setState(STATE_OFF); + if (this.triggerSmoothedReleased()) { + this.setState(STATE_OFF, "trigger released"); this.callEntityMethodOnGrabbed("stopNearTrigger"); return; } @@ -1647,8 +1828,8 @@ function MyController(hand) { }; this.farTrigger = function() { - if (this.triggerSmoothedReleased() && this.secondaryReleased()) { - this.setState(STATE_OFF); + if (this.triggerSmoothedReleased()) { + this.setState(STATE_OFF, "trigger released"); this.callEntityMethodOnGrabbed("stopFarTrigger"); return; } @@ -1665,7 +1846,7 @@ function MyController(hand) { if (intersection.accurate) { this.lastPickTime = now; if (intersection.entityID != this.grabbedEntity) { - this.setState(STATE_OFF); + this.setState(STATE_OFF, "laser moved off of entity"); this.callEntityMethodOnGrabbed("stopFarTrigger"); return; } @@ -1680,6 +1861,10 @@ function MyController(hand) { }; this.offEnter = function() { + this.release(); + }; + + this.release = function() { this.turnLightsOff(); this.turnOffVisualizations(); @@ -1710,6 +1895,10 @@ function MyController(hand) { })); this.grabbedEntity = null; + + if (this.triggerSmoothedGrab()) { + this.waitForTriggerRelease = true; + } }; this.cleanup = function() { @@ -1918,12 +2107,12 @@ Controller.enableMapping(MAPPING_NAME); //the section below allows the grab script to listen for messages that disable either one or both hands. useful for two handed items var handToDisable = 'none'; -function update() { +function update(deltaTime) { if (handToDisable !== LEFT_HAND && handToDisable !== 'both') { - leftController.update(); + leftController.update(deltaTime); } if (handToDisable !== RIGHT_HAND && handToDisable !== 'both') { - rightController.update(); + rightController.update(deltaTime); } } @@ -1950,7 +2139,7 @@ var handleHandMessages = function(channel, message, sender) { data = JSON.parse(message); var selectedController = (data.hand === 'left') ? leftController : rightController; selectedController.release(); - selectedController.setState(STATE_HOLD); + selectedController.setState(STATE_HOLD, "Hifi-Hand-Grab msg received"); selectedController.grabbedEntity = data.entityID; } catch (e) { From 14efd5dc127480f41007d3359de3c1251035c818 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 20 Jun 2016 10:44:09 -0700 Subject: [PATCH 72/93] Disable WANT_DEBUG_STATE --- scripts/system/controllers/handControllerGrab.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 8c5d48667d..8727bbdd2d 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -18,7 +18,7 @@ Script.include("/~/system/libraries/utils.js"); // add lines where the hand ray picking is happening // var WANT_DEBUG = false; -var WANT_DEBUG_STATE = true; +var WANT_DEBUG_STATE = false; var WANT_DEBUG_SEARCH_NAME = null; // @@ -1711,7 +1711,7 @@ function MyController(hand) { return; } - if (this.state == STATE_HOLD && dropDetected && this.triggerSmoothedGrab()) { + if (this.state == STATE_HOLD && dropDetected && this.triggerSmoothedGrab()) { this.setState(STATE_OFF, "drop detected"); this.callEntityMethodOnGrabbed("releaseEquip"); return; From 7ccbc9e6eb6c3ea8efee260e4bd4d7dd56afa038 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 20 Jun 2016 11:22:56 -0700 Subject: [PATCH 73/93] Primary thumb press can be used to drop an equipped object --- scripts/system/controllers/handControllerGrab.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 8727bbdd2d..1471decbfa 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -1711,7 +1711,7 @@ function MyController(hand) { return; } - if (this.state == STATE_HOLD && dropDetected && this.triggerSmoothedGrab()) { + if ((this.state == STATE_HOLD && dropDetected && this.triggerSmoothedGrab()) || this.thumbPressed()) { this.setState(STATE_OFF, "drop detected"); this.callEntityMethodOnGrabbed("releaseEquip"); return; From 35276c38932a153785f6888ec72582834e75fdce Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 20 Jun 2016 11:30:40 -0700 Subject: [PATCH 74/93] Use Array.prototype.filter instead of my hand rolled filter function --- scripts/system/controllers/handControllerGrab.js | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 1471decbfa..bbf9dcb793 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -253,17 +253,6 @@ function restore2DMode() { } } -function filter(array, predicate) { - var i, l = array.length; - var result = []; - for (i = 0; i < l; i++) { - if (predicate(array[i])) { - result.push(array[i]); - } - } - return result; -} - // constructor function EntityPropertiesCache() { this.cache = {}; @@ -1164,7 +1153,7 @@ function MyController(hand) { this.entityPropertyCache.findEntities(handPosition, GRAB_RADIUS); var candidateEntities = this.entityPropertyCache.getEntities(); - var equippableEntities = filter(candidateEntities, function (entity) { + var equippableEntities = candidateEntities.filter(function (entity) { return _this.entityIsEquippable(entity, handPosition); }); @@ -1193,7 +1182,7 @@ function MyController(hand) { this.entityPropertyCache.addEntity(rayPickInfo.entityID); } - var grabbableEntities = filter(candidateEntities, function (entity) { + var grabbableEntities = candidateEntities.filter(function (entity) { return _this.entityIsNearGrabbable(entity, handPosition); }); From a14bbe4e682e18c38d5c9a8fd21420d5bf2f99b7 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 20 Jun 2016 11:40:59 -0700 Subject: [PATCH 75/93] Remove /* style multi-line comments --- .../system/controllers/handControllerGrab.js | 66 +++++++++---------- 1 file changed, 31 insertions(+), 35 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index bbf9dcb793..76e13ce992 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -916,11 +916,11 @@ function MyController(hand) { this.equipHotspotOverlays = []; }; - /** - * Performs ray pick test from the hand controller into the world - * @param {number} which hand to use, RIGHT_HAND or LEFT_HAND - * @returns {object} returns object with two keys entityID and distance - */ + /// + // Performs ray pick test from the hand controller into the world + // @param {number} which hand to use, RIGHT_HAND or LEFT_HAND + // @returns {object} returns object with two keys entityID and distance + // this.calcRayPickInfo = function(hand) { var pose = Controller.getPoseValue((hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand); @@ -1365,14 +1365,12 @@ function MyController(hand) { var handMoved = Vec3.multiply(Vec3.subtract(controllerPositionVSAvatar, this.previousControllerPositionVSAvatar), radius); - // double delta controller rotation - /* - var DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR = 2.0; // object rotates this much more than hand did - var handChange = Quat.multiply(Quat.slerp(this.previousControllerRotation, - controllerRotation, - DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR), - Quat.inverse(this.previousControllerRotation)); - */ + /// double delta controller rotation + // var DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR = 2.0; // object rotates this much more than hand did + // var handChange = Quat.multiply(Quat.slerp(this.previousControllerRotation, + // controllerRotation, + // DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR), + // Quat.inverse(this.previousControllerRotation)); // update the currentObject position and rotation. this.currentObjectPosition = Vec3.sum(this.currentObjectPosition, handMoved); @@ -1426,28 +1424,26 @@ function MyController(hand) { } } - /* - var defaultConstraintData = { - axisStart: false, - axisEnd: false - } - - var constraintData = getEntityCustomData('lightModifierKey', this.grabbedEntity, defaultConstraintData); - var clampedVector; - var targetPosition; - if (constraintData.axisStart !== false) { - clampedVector = this.projectVectorAlongAxis(this.currentObjectPosition, - constraintData.axisStart, - constraintData.axisEnd); - targetPosition = clampedVector; - } else { - targetPosition = { - x: this.currentObjectPosition.x, - y: this.currentObjectPosition.y, - z: this.currentObjectPosition.z - } - } - */ + // var defaultConstraintData = { + // axisStart: false, + // axisEnd: false + // } + // + // var constraintData = getEntityCustomData('lightModifierKey', this.grabbedEntity, defaultConstraintData); + // var clampedVector; + // var targetPosition; + // if (constraintData.axisStart !== false) { + // clampedVector = this.projectVectorAlongAxis(this.currentObjectPosition, + // constraintData.axisStart, + // constraintData.axisEnd); + // targetPosition = clampedVector; + // } else { + // targetPosition = { + // x: this.currentObjectPosition.x, + // y: this.currentObjectPosition.y, + // z: this.currentObjectPosition.z + // } + // } var handPosition = this.getHandPosition(); From 359483d9ba617637b01ad7f18e168d0476f8a5e2 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 20 Jun 2016 11:43:42 -0700 Subject: [PATCH 76/93] coding standard fix --- scripts/system/controllers/handControllerGrab.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 76e13ce992..8cef751e07 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -935,9 +935,11 @@ function MyController(hand) { length: PICK_MAX_DISTANCE }; - var result = { entityID: null, - searchRay: pickRay, - distance: PICK_MAX_DISTANCE }; + var result = { + entityID: null, + searchRay: pickRay, + distance: PICK_MAX_DISTANCE + }; var now = Date.now(); if (now - this.lastPickTime < MSECS_PER_SEC / PICKS_PER_SECOND_PER_HAND) { From cd1780efefc1f0ad1a8cbad53e98f08631ecfbb0 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Mon, 20 Jun 2016 13:21:06 -0700 Subject: [PATCH 77/93] brush clearing --- .../controllers/handControllerPointer.js | 115 ++++-------------- 1 file changed, 27 insertions(+), 88 deletions(-) diff --git a/scripts/system/controllers/handControllerPointer.js b/scripts/system/controllers/handControllerPointer.js index 0f1e23b45c..0fe199098d 100644 --- a/scripts/system/controllers/handControllerPointer.js +++ b/scripts/system/controllers/handControllerPointer.js @@ -14,15 +14,11 @@ // // Control the "mouse" using hand controller. (HMD and desktop.) -// For now: -// Hydra thumb button 3 is left-mouse, button 4 is right-mouse. -// A click in the center of the vive thumb pad is left mouse. Vive menu button is context menu (right mouse). // First-person only. // Starts right handed, but switches to whichever is free: Whichever hand was NOT most recently squeezed. // (For now, the thumb buttons on both controllers are always on.) -// When over a HUD element, the reticle is shown where the active hand controller beam intersects the HUD. -// Otherwise, the active hand controller shows a red ball where a click will act. - +// When partially squeezing over a HUD element, a laser or the reticle is shown where the active hand +// controller beam intersects the HUD. // UTILITIES ------------- @@ -102,7 +98,7 @@ function Trigger(label) { } that.state = state; }; - // Answer a controller source function (answering either 0.0 or 1.0), with hysteresis. + // Answer a controller source function (answering either 0.0 or 1.0). that.partial = function () { return that.state ? 1.0 : 0.0; // either 'partial' or 'full' }; @@ -369,24 +365,9 @@ clickMapping.enable(); // VISUAL AID ----------- // Same properties as handControllerGrab search sphere -var BALL_SIZE = 0.011; -var BALL_ALPHA = 0.5; -var LASER_SEARCH_COLOR_XYZW = {x: 10 / 255, y: 10 / 255, z: 255 / 255, w: BALL_ALPHA}; -var LASER_TRIGGER_COLOR_XYZW = {x: 250 / 255, y: 10 / 255, z: 10 / 255, w: BALL_ALPHA}; -var fakeProjectionBall = Overlays.addOverlay("sphere", { - size: 5 * BALL_SIZE, - color: {red: 255, green: 10, blue: 10}, - ignoreRayIntersection: true, - alpha: BALL_ALPHA, - visible: false, - solid: true, - drawInFront: true // Even when burried inside of something, show it. -}); -var overlays = [fakeProjectionBall]; // If we want to try showing multiple balls and lasers. -Script.scriptEnding.connect(function () { - overlays.forEach(Overlays.deleteOverlay); -}); -var visualizationIsShowing = false; // Not whether it desired, but simply whether it is. Just an optimziation. +var LASER_ALPHA = 0.5; +var LASER_SEARCH_COLOR_XYZW = {x: 10 / 255, y: 10 / 255, z: 255 / 255, w: LASER_ALPHA}; +var LASER_TRIGGER_COLOR_XYZW = {x: 250 / 255, y: 10 / 255, z: 10 / 255, w: LASER_ALPHA}; var SYSTEM_LASER_DIRECTION = {x: 0, y: 0, z: -1}; var systemLaserOn = false; function clearSystemLaser() { @@ -401,81 +382,30 @@ function setColoredLaser() { // answer trigger state if lasers supported, else f return HMD.setHandLasers(activeHudLaser, true, color, SYSTEM_LASER_DIRECTION) && activeTrigger.state; } -function turnOffVisualization(optionalEnableClicks) { // because we're showing cursor on HUD - if (!optionalEnableClicks) { - expireMouseCursor(); - clearSystemLaser(); - } else if (activeTrigger.state && (!systemLaserOn || (systemLaserOn !== activeTrigger.state))) { // last=>wrong color - // If the active plugin doesn't implement hand lasers, show the mouse reticle instead. - systemLaserOn = setColoredLaser(); - Reticle.visible = !systemLaserOn; - } else if ((systemLaserOn || Reticle.visible) && !activeTrigger.state) { - clearSystemLaser(); - Reticle.visible = false; - } - - if (!visualizationIsShowing) { - return; - } - visualizationIsShowing = false; - overlays.forEach(function (overlay) { - Overlays.editOverlay(overlay, {visible: false}); - }); -} -var MAX_RAY_SCALE = 32000; // Anything large. It's a scale, not a distance. -function updateVisualization(controllerPosition, controllerDirection, hudPosition3d, hudPosition2d) { - ignore(controllerPosition, controllerDirection, hudPosition2d); - clearSystemLaser(); - // Show an indication of where the cursor will appear when crossing a HUD element, - // and where in-world clicking will occur. - // - // There are a number of ways we could do this, but for now, it's a blue sphere that rolls along - // the HUD surface, and a red sphere that rolls along the 3d objects that will receive the click. - // We'll leave it to other scripts (like handControllerGrab) to show a search beam when desired. - - function intersection3d(position, direction) { - // Answer in-world intersection (entity or 3d overlay), or way-out point - var pickRay = {origin: position, direction: direction}; - var result = findRayIntersection(pickRay); - return result.intersects ? result.intersection : Vec3.sum(position, Vec3.multiply(MAX_RAY_SCALE, direction)); - } - - visualizationIsShowing = true; - // We'd rather in-world interactions be done at the termination of the hand beam - // -- intersection3d(controllerPosition, controllerDirection). Maybe have handControllerGrab - // direclty manipulate both entity and 3d overlay objects. - // For now, though, we present a false projection of the cursor onto whatever is below it. This is - // different from the hand beam termination because the false projection is from the camera, while - // the hand beam termination is from the hand. - /* // FIXME: We can tighten this up later, once we know what will and won't be included. - var eye = Camera.getPosition(); - var falseProjection = intersection3d(eye, Vec3.subtract(hudPosition3d, eye)); - Overlays.editOverlay(fakeProjectionBall, {visible: true, position: falseProjection}); - */ - Reticle.visible = false; - - return visualizationIsShowing; // In case we change caller to act conditionally. -} // MAIN OPERATIONS ----------- // function update() { var now = Date.now(); + function off() { + expireMouseCursor(); + clearSystemLaser(); + } if (!handControllerLockOut.expired(now)) { - return turnOffVisualization(); // Let them use mouse it in peace. + return off(); // Let them use mouse it in peace. } if (!Menu.isOptionChecked("First Person")) { - return turnOffVisualization(); // What to do? menus can be behind hand! + return off(); // What to do? menus can be behind hand! } if (!Window.hasFocus() || !Reticle.allowMouseCapture) { - return turnOffVisualization(); // Don't mess with other apps or paused mouse activity + return off(); // Don't mess with other apps or paused mouse activity } leftTrigger.update(); rightTrigger.update(); var controllerPose = Controller.getPoseValue(activeHand); // Valid if any plugged-in hand controller is "on". (uncradled Hydra, green-lighted Vive...) if (!controllerPose.valid) { - return turnOffVisualization(); // Controller is cradled. + return off(); // Controller is cradled. } var controllerPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, controllerPose.translation), MyAvatar.position); @@ -487,7 +417,7 @@ function update() { if (Menu.isOptionChecked("Overlays")) { // With our hud resetting strategy, hudPoint3d should be valid here print('Controller is parallel to HUD'); // so let us know that our assumptions are wrong. } - return turnOffVisualization(); + return off(); } var hudPoint2d = overlayFromWorldPoint(hudPoint3d); @@ -499,13 +429,22 @@ function update() { if (HMD.active) { Reticle.depth = hudReticleDistance(); } - return turnOffVisualization(true); + if (activeTrigger.state && (!systemLaserOn || (systemLaserOn !== activeTrigger.state))) { // last=>wrong color + // If the active plugin doesn't implement hand lasers, show the mouse reticle instead. + systemLaserOn = setColoredLaser(); + Reticle.visible = !systemLaserOn; + } else if ((systemLaserOn || Reticle.visible) && !activeTrigger.state) { + clearSystemLaser(); + Reticle.visible = false; + } + return; } // We are not pointing at a HUD element (but it could be a 3d overlay). if (!activeTrigger.state) { - return turnOffVisualization(); // No trigger (with hysteresis). + return off(); // No trigger } - updateVisualization(controllerPosition, controllerDirection, hudPoint3d, hudPoint2d); + clearSystemLaser(); + Reticle.visible = false; } var UPDATE_INTERVAL = 50; // milliseconds. Script.update is too frequent. From 8792812884814237bff55fcded366d88bc5362fa Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 20 Jun 2016 13:26:41 -0700 Subject: [PATCH 78/93] Change raw for loop to Array.prototype.forEach instead --- scripts/system/controllers/handControllerGrab.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 8cef751e07..4143cdd403 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -47,7 +47,6 @@ var DISTANCE_HOLDING_ACTION_TIMEFRAME = 0.1; // how quickly objects move to thei var DISTANCE_HOLDING_UNITY_MASS = 1200; // The mass at which the distance holding action timeframe is unmodified var DISTANCE_HOLDING_UNITY_DISTANCE = 6; // The distance at which the distance holding action timeframe is unmodified var MOVE_WITH_HEAD = true; // experimental head-control of distantly held objects -var FAR_TO_NEAR_GRAB_PADDING_FACTOR = 1.2; var NO_INTERSECT_COLOR = { red: 10, @@ -262,10 +261,10 @@ EntityPropertiesCache.prototype.clear = function() { }; EntityPropertiesCache.prototype.findEntities = function(position, radius) { var entities = Entities.findEntities(position, radius); - var i, l = entities.length; - for (i = 0; i < l; i++) { - this.addEntity(entities[i]); - } + var _this = this; + entities.forEach(function (x) { + _this.addEntity(x); + }); }; EntityPropertiesCache.prototype.addEntity = function(entityID) { var props = Entities.getEntityProperties(entityID, GRABBABLE_PROPERTIES); From 408f65100be61ec3eae0e651b61a1217f778b12f Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 20 Jun 2016 13:34:05 -0700 Subject: [PATCH 79/93] Bug fix for dropping near-grabbed entities via thump press. --- scripts/system/controllers/handControllerGrab.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 4143cdd403..4300c32171 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -1697,10 +1697,18 @@ function MyController(hand) { return; } - if ((this.state == STATE_HOLD && dropDetected && this.triggerSmoothedGrab()) || this.thumbPressed()) { - this.setState(STATE_OFF, "drop detected"); - this.callEntityMethodOnGrabbed("releaseEquip"); - return; + if (this.state == STATE_HOLD) { + if (dropDetected && this.triggerSmoothedGrab()) { + this.setState(STATE_OFF, "drop gesture detected"); + this.callEntityMethodOnGrabbed("releaseEquip"); + return; + } + + if (this.thumbPressed()) { + this.setState(STATE_OFF, "drop via thumb press"); + this.callEntityMethodOnGrabbed("releaseEquip"); + return; + } } this.heartBeat(this.grabbedEntity); From e5159ad213a437e63feb509796f3961ce9bd8399 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 20 Jun 2016 14:50:15 -0700 Subject: [PATCH 80/93] Added "Developer > Hands > Drop Without Shake" menu option --- .../system/controllers/handControllerGrab.js | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 4300c32171..c85e113bc4 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -37,6 +37,7 @@ var THUMB_ON_VALUE = 0.5; var HAND_HEAD_MIX_RATIO = 0.0; // 0 = only use hands for search/move. 1 = only use head for search/move. var PICK_WITH_HAND_RAY = true; +var DROP_WITHOUT_SHAKE = false; // // distant manipulation @@ -1589,7 +1590,7 @@ function MyController(hand) { print("handMove = " + this.fastHandMoveDetected + ", handIsUpsideDown = " + handIsUpsideDown); } - return this.fastHandMoveDetected && handIsUpsideDown; + return (DROP_WITHOUT_SHAKE || this.fastHandMoveDetected) && handIsUpsideDown; }; this.nearGrabbingEnter = function() { @@ -2170,6 +2171,24 @@ function cleanup() { leftController.cleanup(); Controller.disableMapping(MAPPING_NAME); Reticle.setVisible(true); + Menu.removeMenuItem("Developer > Hands", "Drop Without Shake"); } + Script.scriptEnding.connect(cleanup); Script.update.connect(update); + +Menu.addMenuItem({ + menuName: "Developer > Hands", + menuItemName: "Drop Without Shake", + isCheckable: true, + isChecked: DROP_WITHOUT_SHAKE +}); + +function handleMenuItemEvent(menuItem) { + if (menuItem === "Drop Without Shake") { + DROP_WITHOUT_SHAKE = Menu.isOptionChecked("Drop Without Shake"); + } +} + +Menu.menuItemEvent.connect(handleMenuItemEvent); + From 37e9f666390c6f92dcdd0d731d2404b5fa2b4781 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Mon, 20 Jun 2016 15:41:21 -0700 Subject: [PATCH 81/93] Additional toolbar work --- interface/resources/qml/hifi/Desktop.qml | 9 +- .../resources/qml/hifi/toolbars/Toolbar.qml | 128 +++++++++++------- .../qml/hifi/toolbars/ToolbarButton.qml | 34 ++++- interface/resources/qml/windows/Frame.qml | 2 +- interface/resources/qml/windows/ToolFrame.qml | 63 +++++++-- .../scripting/ToolbarScriptingInterface.cpp | 9 -- scripts/developer/tests/toolbarTest.js | 2 +- tests/ui/qml/main.qml | 2 +- 8 files changed, 174 insertions(+), 75 deletions(-) diff --git a/interface/resources/qml/hifi/Desktop.qml b/interface/resources/qml/hifi/Desktop.qml index c14d55cb00..169542c0f0 100644 --- a/interface/resources/qml/hifi/Desktop.qml +++ b/interface/resources/qml/hifi/Desktop.qml @@ -3,12 +3,12 @@ import QtQuick.Controls 1.4 import QtWebEngine 1.1; import Qt.labs.settings 1.0 -import "../desktop" +import "../desktop" as OriginalDesktop import ".." import "." import "./toolbars" -Desktop { +OriginalDesktop.Desktop { id: desktop MouseArea { @@ -54,12 +54,13 @@ Desktop { WebEngine.settings.localContentCanAccessRemoteUrls = true; var sysToolbar = desktop.getToolbar("com.highfidelity.interface.toolbar.system"); - //toolbars[sysToolbar.objectName] = sysToolbar var toggleHudButton = sysToolbar.addButton({ + objectName: "hudToggle", imageURL: "../../../icons/hud-01.svg", visible: true, - + pinned: true, }); + toggleHudButton.yOffset = Qt.binding(function(){ return desktop.pinned ? 50 : 0 }); diff --git a/interface/resources/qml/hifi/toolbars/Toolbar.qml b/interface/resources/qml/hifi/toolbars/Toolbar.qml index 35c816569b..75c06e4199 100644 --- a/interface/resources/qml/hifi/toolbars/Toolbar.qml +++ b/interface/resources/qml/hifi/toolbars/Toolbar.qml @@ -7,14 +7,16 @@ import "." Window { id: window - frame: ToolFrame { } + frame: ToolFrame { + horizontalSpacers: horizontal + verticalSpacers: !horizontal + } hideBackground: true resizable: false destroyOnCloseButton: false destroyOnHidden: false closable: false shown: true - pinned: true width: content.width height: content.height visible: true @@ -32,54 +34,77 @@ Window { } onHorizontalChanged: { - var oldParent = horizontal ? column : row; var newParent = horizontal ? row : column; - var move = []; - - var i; - for (i in oldParent.children) { - var child = oldParent.children[i]; - if (child.spacer) { - continue; - } - move.push(oldParent.children[i]); - } - for (i in move) { - move[i].parent = newParent; + for (var i in buttons) { + var child = buttons[i]; + child.parent = newParent; if (horizontal) { - move[i].y = 0 + child.y = 0 } else { - move[i].x = 0 + child.x = 0 } } - fixSpacers(); } Item { id: content implicitHeight: horizontal ? row.height : column.height implicitWidth: horizontal ? row.width : column.width + Row { id: row spacing: 6 - visible: window.horizontal - Rectangle{ readonly property bool spacer: true; id: rowSpacer1; width: 1; height: row.height } - Rectangle{ readonly property bool spacer: true; id: rowSpacer2; width: 1; height: row.height } - Rectangle{ readonly property bool spacer: true; id: rowSpacer3; width: 1; height: row.height } - Rectangle{ readonly property bool spacer: true; id: rowSpacer4; width: 1; height: row.height } } Column { id: column spacing: 6 - visible: !window.horizontal - Rectangle{ readonly property bool spacer: true; id: colSpacer1; width: column.width; height: 1 } - Rectangle{ readonly property bool spacer: true; id: colSpacer2; width: column.width; height: 1 } - Rectangle{ readonly property bool spacer: true; id: colSpacer3; width: column.width; height: 1 } - Rectangle{ readonly property bool spacer: true; id: colSpacer4; width: column.width; height: 1 } } Component { id: toolbarButtonBuilder; ToolbarButton { } } + + Connections { + target: desktop + onPinnedChanged: { + if (!window.pinned) { + return; + } + var newPinned = desktop.pinned; + for (var i in buttons) { + var child = buttons[i]; + if (desktop.pinned) { + if (!child.pinned) { + child.visible = false; + } + } else { + child.visible = true; + } + } + } + } + } + + + function findButtonIndex(name) { + if (!name) { + return -1; + } + + for (var i in buttons) { + var child = buttons[i]; + if (child.objectName === name) { + return i; + } + } + return -1; + } + + function findButton(name) { + var index = findButtonIndex(name); + if (index < 0) { + return; + } + return buttons[index]; } function addButton(properties) { @@ -88,30 +113,39 @@ Window { // If a name is specified, then check if there's an existing button with that name // and return it if so. This will allow multiple clients to listen to a single button, // and allow scripts to be idempotent so they don't duplicate buttons if they're reloaded - if (properties.objectName) { - for (var i in buttons) { - var child = buttons[i]; - if (child.objectName === properties.objectName) { - return child; - } - } + var result = findButton(properties.objectName); + if (result) { + return result; } - properties.toolbar = this; - var result = toolbarButtonBuilder.createObject(container, properties); + properties.opacity = 0; + result = toolbarButtonBuilder.createObject(container, properties); buttons.push(result); - fixSpacers(); + result.opacity = 1; + updatePinned(); return result; } - function fixSpacers() { - colSpacer3.parent = null - colSpacer4.parent = null - rowSpacer3.parent = null - rowSpacer4.parent = null - colSpacer3.parent = column - colSpacer4.parent = column - rowSpacer3.parent = row - rowSpacer4.parent = row + function removeButton(name) { + var index = findButtonIndex(name); + if (index < -1) { + console.warn("Tried to remove non-existent button " + name); + return; + } + buttons[index].destroy(); + buttons.splice(index, 1); + updatePinned(); + } + + function updatePinned() { + var newPinned = false; + for (var i in buttons) { + var child = buttons[i]; + if (child.pinned) { + newPinned = true; + break; + } + } + pinned = newPinned; } } diff --git a/interface/resources/qml/hifi/toolbars/ToolbarButton.qml b/interface/resources/qml/hifi/toolbars/ToolbarButton.qml index a8514689e8..a3be4533d2 100644 --- a/interface/resources/qml/hifi/toolbars/ToolbarButton.qml +++ b/interface/resources/qml/hifi/toolbars/ToolbarButton.qml @@ -7,11 +7,40 @@ Item { property alias alpha: button.opacity property var subImage; property int yOffset: 0 + property int buttonState: 0 property var toolbar; property real size: 50 // toolbar ? toolbar.buttonSize : 50 width: size; height: size + property bool pinned: false clip: true + Behavior on opacity { + NumberAnimation { + duration: 150 + easing.type: Easing.InOutCubic + } + } + + property alias fadeTargetProperty: button.opacity + + onFadeTargetPropertyChanged: { + visible = (fadeTargetProperty !== 0.0); + } + + onVisibleChanged: { + if ((!visible && fadeTargetProperty != 0.0) || (visible && fadeTargetProperty == 0.0)) { + var target = visible; + visible = !visible; + fadeTargetProperty = target ? 1.0 : 0.0; + return; + } + } + + + onButtonStateChanged: { + yOffset = size * buttonState + } + Component.onCompleted: { if (subImage) { if (subImage.y) { @@ -30,10 +59,7 @@ Item { MouseArea { anchors.fill: parent - onClicked: { - console.log("Clicked on button " + image.source + " named " + button.objectName) - button.clicked(); - } + onClicked: button.clicked(); } } diff --git a/interface/resources/qml/windows/Frame.qml b/interface/resources/qml/windows/Frame.qml index bc8ecc35ec..88d8c3ad41 100644 --- a/interface/resources/qml/windows/Frame.qml +++ b/interface/resources/qml/windows/Frame.qml @@ -59,7 +59,7 @@ Item { height: 1.66 * window.height x: (window.width - width) / 2 y: window.height / 2 - 0.375 * height - visible: gradientsSupported && window && window.focus && pane.visible + visible: gradientsSupported && window && window.focus && window.content.visible gradient: Gradient { // GradientStop position 0.5 is at full circumference of circle that fits inside the square. GradientStop { position: 0.0; color: "#ff000000" } // black, 100% opacity diff --git a/interface/resources/qml/windows/ToolFrame.qml b/interface/resources/qml/windows/ToolFrame.qml index ac1093092e..eff5fc0377 100644 --- a/interface/resources/qml/windows/ToolFrame.qml +++ b/interface/resources/qml/windows/ToolFrame.qml @@ -16,20 +16,67 @@ import "../styles-uit" Frame { HifiConstants { id: hifi } + property bool horizontalSpacers: false + property bool verticalSpacers: false Rectangle { // Dialog frame id: frameContent readonly property int frameMargin: 6 - readonly property int frameMarginLeft: frameMargin - readonly property int frameMarginRight: frameMargin - readonly property int frameMarginTop: frameMargin - readonly property int frameMarginBottom: frameMargin + readonly property int frameMarginLeft: frameMargin + (horizontalSpacers ? 12 : 0) + readonly property int frameMarginRight: frameMargin + (horizontalSpacers ? 12 : 0) + readonly property int frameMarginTop: frameMargin + (verticalSpacers ? 12 : 0) + readonly property int frameMarginBottom: frameMargin + (verticalSpacers ? 12 : 0) + + Rectangle { + visible: horizontalSpacers + anchors.left: parent.left + anchors.leftMargin: 6 + anchors.verticalCenter: parent.verticalCenter + width: 8 + height: window.height + color: "gray"; + radius: 4 + } + + Rectangle { + visible: horizontalSpacers + anchors.right: parent.right + anchors.rightMargin: 6 + anchors.verticalCenter: parent.verticalCenter + width: 8 + height: window.height + color: "gray"; + radius: 4 + } + + Rectangle { + visible: verticalSpacers + anchors.top: parent.top + anchors.topMargin: 6 + anchors.horizontalCenter: parent.horizontalCenter + height: 8 + width: window.width + color: "gray"; + radius: 4 + } + + Rectangle { + visible: verticalSpacers + anchors.bottom: parent.bottom + anchors.bottomMargin: 6 + anchors.horizontalCenter: parent.horizontalCenter + height: 8 + width: window.width + color: "gray"; + radius: 4 + } + anchors { - topMargin: -frameMargin - leftMargin: -frameMargin - rightMargin: -frameMargin - bottomMargin: -frameMargin + leftMargin: -frameMarginLeft + rightMargin: -frameMarginRight + topMargin: -frameMarginTop + bottomMargin: -frameMarginBottom } anchors.fill: parent color: hifi.colors.baseGrayHighlight40 diff --git a/interface/src/scripting/ToolbarScriptingInterface.cpp b/interface/src/scripting/ToolbarScriptingInterface.cpp index fd96b6a809..82332b3187 100644 --- a/interface/src/scripting/ToolbarScriptingInterface.cpp +++ b/interface/src/scripting/ToolbarScriptingInterface.cpp @@ -15,15 +15,6 @@ class QmlWrapper : public QObject { public: QmlWrapper(QObject* qmlObject, QObject* parent = nullptr) : QObject(parent), _qmlObject(qmlObject) { - - const QMetaObject *metaobject = qmlObject->metaObject(); - int count = metaobject->propertyCount(); - qDebug() << "Scanning properties for " << qmlObject; - for (int i = 0; i < count; ++i) { - QMetaProperty metaproperty = metaobject->property(i); - const char *name = metaproperty.name(); - qDebug() << "Property " << name; - } } Q_INVOKABLE void writeProperty(QString propertyName, QVariant propertyValue) { diff --git a/scripts/developer/tests/toolbarTest.js b/scripts/developer/tests/toolbarTest.js index 20f349929f..e21fbd8e19 100644 --- a/scripts/developer/tests/toolbarTest.js +++ b/scripts/developer/tests/toolbarTest.js @@ -13,7 +13,7 @@ var toolBar = (function() { newZoneButton, newParticleButton - var toolIconUrl = Script.resolvePath("assets/images/tools/"); + var toolIconUrl = Script.resolvePath("../../system/assets/images/tools/"); function initialize() { print("Toolbars: " + Toolbars); diff --git a/tests/ui/qml/main.qml b/tests/ui/qml/main.qml index 19f6a55bfd..47d0f6d601 100644 --- a/tests/ui/qml/main.qml +++ b/tests/ui/qml/main.qml @@ -57,7 +57,7 @@ ApplicationWindow { "particle-01.svg", ] property int iconIndex: 0 - readonly property string toolIconUrl: "file:///C:/Users/bdavi/git/hifi/scripts/system/assets/images/tools/" + readonly property string toolIconUrl: "../../../../../scripts/system/assets/images/tools/" text: "Create Button" onClicked: { var name = icons[iconIndex]; From 8817bbea0921af134dd522eb21f593ed5ba058d6 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 20 Jun 2016 15:53:33 -0700 Subject: [PATCH 82/93] reduce log spam --- scripts/system/controllers/handControllerGrab.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index c85e113bc4..4afbae451f 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -984,7 +984,7 @@ function MyController(hand) { var props = this.entityPropertyCache.getProps(entityID); var distance = Vec3.distance(props.position, handPosition); var grabProps = this.entityPropertyCache.getGrabProps(entityID); - var debug = true;//(WANT_DEBUG_SEARCH_NAME && props.name === WANT_DEBUG_SEARCH_NAME); + var debug = (WANT_DEBUG_SEARCH_NAME && props.name === WANT_DEBUG_SEARCH_NAME); var refCount = ("refCount" in grabProps) ? grabProps.refCount : 0; if (refCount > 0) { From bfb697bc7789f5cd6dbb073be47f846fc39a66da Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Mon, 20 Jun 2016 17:04:23 -0700 Subject: [PATCH 83/93] No hysteresis. --- scripts/system/controllers/handControllerPointer.js | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/scripts/system/controllers/handControllerPointer.js b/scripts/system/controllers/handControllerPointer.js index 0fe199098d..c94325d3c0 100644 --- a/scripts/system/controllers/handControllerPointer.js +++ b/scripts/system/controllers/handControllerPointer.js @@ -85,15 +85,10 @@ function Trigger(label) { state = 'full'; } else if (that.triggerSmoothedReleased()) { state = null; - // These depend on previous state: - // null -> squeezed ==> partial - // full -> !squeezed ==> partial - // Otherwise no change. } else if (that.triggerSmoothedSqueezed()) { - if (!state) { - state = 'partial'; - } - } else if (state === 'full') { + // Another way to do this would be to have hysteresis in this branch, but that seems to make things harder to use. + // In particular, the vive has a nice detent as you release off of full, and we want that to be a transition from + // full to partial. state = 'partial'; } that.state = state; From 0bc4beeffd4e85d353ece1219cdd3697a2b14ce6 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Mon, 20 Jun 2016 17:09:30 -0700 Subject: [PATCH 84/93] Fix initial toolbar positioning --- interface/resources/qml/desktop/Desktop.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/desktop/Desktop.qml b/interface/resources/qml/desktop/Desktop.qml index e269a63cde..58d1cba1b8 100644 --- a/interface/resources/qml/desktop/Desktop.qml +++ b/interface/resources/qml/desktop/Desktop.qml @@ -65,7 +65,7 @@ FocusScope { var oldChildren = expectedChildren; var newChildren = d.getRepositionChildren(); - if (oldRecommendedRect != Qt.rect(0,0,0,0) + if (oldRecommendedRect != Qt.rect(0,0,0,0) && oldRecommendedRect != Qt.rect(0,0,1,1) && (oldRecommendedRect != newRecommendedRect || oldChildren != newChildren) ) { From 2ed88bca6d569db24f49fe59a6afa9a280b99f60 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 20 Jun 2016 17:24:36 -0700 Subject: [PATCH 85/93] Bug fix for bow and arrow. * before this change the releaseEquip message was not getting sent to the bow, which it would use to re-enable the grab script via the 'Hifi-Hand-Disabler' msg. --- .../system/controllers/handControllerGrab.js | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 4afbae451f..f1ecc15392 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -1334,8 +1334,8 @@ function MyController(hand) { this.distanceHolding = function() { if (this.triggerSmoothedReleased()) { - this.setState(STATE_OFF, "trigger released"); this.callEntityMethodOnGrabbed("releaseGrab"); + this.setState(STATE_OFF, "trigger released"); return; } @@ -1693,21 +1693,21 @@ function MyController(hand) { var dropDetected = this.dropGestureProcess(deltaTime); if (this.state == STATE_NEAR_GRABBING && this.triggerSmoothedReleased()) { - this.setState(STATE_OFF, "trigger released"); this.callEntityMethodOnGrabbed("releaseGrab"); + this.setState(STATE_OFF, "trigger released"); return; } if (this.state == STATE_HOLD) { if (dropDetected && this.triggerSmoothedGrab()) { - this.setState(STATE_OFF, "drop gesture detected"); this.callEntityMethodOnGrabbed("releaseEquip"); + this.setState(STATE_OFF, "drop gesture detected"); return; } if (this.thumbPressed()) { - this.setState(STATE_OFF, "drop via thumb press"); this.callEntityMethodOnGrabbed("releaseEquip"); + this.setState(STATE_OFF, "drop via thumb press"); return; } } @@ -1717,8 +1717,8 @@ function MyController(hand) { var props = Entities.getEntityProperties(this.grabbedEntity, ["localPosition", "parentID", "position", "rotation"]); if (!props.position) { // server may have reset, taking our equipped entity with it. move back to "off" stte - this.setState(STATE_OFF, "entity has no position property"); this.callEntityMethodOnGrabbed("releaseGrab"); + this.setState(STATE_OFF, "entity has no position property"); return; } @@ -1737,12 +1737,13 @@ function MyController(hand) { // for whatever reason, the held/equipped entity has been pulled away. ungrab or unequip. print("handControllerGrab -- autoreleasing held or equipped item because it is far from hand." + props.parentID + " " + vec3toStr(props.position)); - this.setState(STATE_OFF, "held object too far away"); + if (this.state == STATE_NEAR_GRABBING) { this.callEntityMethodOnGrabbed("releaseGrab"); } else { // this.state == STATE_HOLD this.callEntityMethodOnGrabbed("releaseEquip"); } + this.setState(STATE_OFF, "held object too far away"); return; } } @@ -1815,8 +1816,8 @@ function MyController(hand) { this.nearTrigger = function() { if (this.triggerSmoothedReleased()) { - this.setState(STATE_OFF, "trigger released"); this.callEntityMethodOnGrabbed("stopNearTrigger"); + this.setState(STATE_OFF, "trigger released"); return; } this.callEntityMethodOnGrabbed("continueNearTrigger"); @@ -1824,8 +1825,8 @@ function MyController(hand) { this.farTrigger = function() { if (this.triggerSmoothedReleased()) { - this.setState(STATE_OFF, "trigger released"); this.callEntityMethodOnGrabbed("stopFarTrigger"); + this.setState(STATE_OFF, "trigger released"); return; } @@ -1841,8 +1842,8 @@ function MyController(hand) { if (intersection.accurate) { this.lastPickTime = now; if (intersection.entityID != this.grabbedEntity) { - this.setState(STATE_OFF, "laser moved off of entity"); this.callEntityMethodOnGrabbed("stopFarTrigger"); + this.setState(STATE_OFF, "laser moved off of entity"); return; } if (intersection.intersects) { From 2ac6dc8798995490cf4a9f02cf83aac85c773ab8 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Tue, 21 Jun 2016 09:48:01 -0700 Subject: [PATCH 86/93] do not tilt virtual keyboard --- plugins/openvr/src/OpenVrHelpers.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/openvr/src/OpenVrHelpers.cpp b/plugins/openvr/src/OpenVrHelpers.cpp index dc38aa0a0a..4f02c0384d 100644 --- a/plugins/openvr/src/OpenVrHelpers.cpp +++ b/plugins/openvr/src/OpenVrHelpers.cpp @@ -138,7 +138,7 @@ void showOpenVrKeyboard(bool show = true) { if (vr::VROverlayError_None == showKeyboardResult) { _keyboardShown = true; // Try to position the keyboard slightly below where the user is looking. - mat4 headPose = toGlm(_trackedDevicePose[0].mDeviceToAbsoluteTracking); + mat4 headPose = cancelOutRollAndPitch(toGlm(_trackedDevicePose[0].mDeviceToAbsoluteTracking)); mat4 keyboardTransform = glm::translate(headPose, vec3(0, -0.5, -1)); keyboardTransform = keyboardTransform * glm::rotate(mat4(), 3.14159f / 4.0f, vec3(-1, 0, 0)); auto keyboardTransformVr = toOpenVr(keyboardTransform); From 972c292857d76ccdd92c38cdedbb500b369772c4 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 21 Jun 2016 11:59:11 -0700 Subject: [PATCH 87/93] Fix for equip-hotspot being visible for whiteboard markers --- .../system/controllers/handControllerGrab.js | 37 ++++++++----------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index f1ecc15392..39d85f1224 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -879,28 +879,23 @@ function MyController(hand) { var entities = Entities.findEntities(MyAvatar.position, HOTSPOT_DRAW_DISTANCE); var i, l = entities.length; for (i = 0; i < l; i++) { - - // is this entity equipable? - var grabData = getEntityCustomData(GRABBABLE_DATA_KEY, entities[i], undefined); var grabProps = Entities.getEntityProperties(entities[i], GRABBABLE_PROPERTIES); - if (grabData) { - // does this entity have an attach point? - var wearableData = getEntityCustomData("wearable", entities[i], undefined); - if (wearableData && wearableData.joints) { - var handJointName = this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"; - if (wearableData.joints[handJointName]) { - // draw the hotspot - this.equipHotspotOverlays.push(Overlays.addOverlay("sphere", { - position: grabProps.position, - size: 0.2, - color: { red: 90, green: 255, blue: 90 }, - alpha: 0.7, - solid: true, - visible: true, - ignoreRayIntersection: false, - drawInFront: false - })); - } + // does this entity have an attach point? + var wearableData = getEntityCustomData("wearable", entities[i], undefined); + if (wearableData && wearableData.joints) { + var handJointName = this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"; + if (wearableData.joints[handJointName]) { + // draw the hotspot + this.equipHotspotOverlays.push(Overlays.addOverlay("sphere", { + position: grabProps.position, + size: 0.2, + color: { red: 90, green: 255, blue: 90 }, + alpha: 0.7, + solid: true, + visible: true, + ignoreRayIntersection: false, + drawInFront: false + })); } } } From f3bad3a63b5df2f2622e539bb02ff2c6f17afa23 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Tue, 21 Jun 2016 12:43:02 -0700 Subject: [PATCH 88/93] Don't trigger keyboard on revealing the overlay layer --- interface/resources/qml/desktop/Desktop.qml | 5 ++++ plugins/openvr/src/OpenVrDisplayPlugin.cpp | 2 +- plugins/openvr/src/OpenVrHelpers.cpp | 28 +++++++++++++++++--- plugins/openvr/src/OpenVrHelpers.h | 3 ++- plugins/openvr/src/ViveControllerManager.cpp | 2 +- 5 files changed, 34 insertions(+), 6 deletions(-) diff --git a/interface/resources/qml/desktop/Desktop.qml b/interface/resources/qml/desktop/Desktop.qml index 58d1cba1b8..9f10cfc64a 100644 --- a/interface/resources/qml/desktop/Desktop.qml +++ b/interface/resources/qml/desktop/Desktop.qml @@ -297,6 +297,9 @@ FocusScope { onPinnedChanged: { if (pinned) { + nullFocus.focus = true; + nullFocus.forceActiveFocus(); + // recalculate our non-pinned children hiddenChildren = d.findMatchingChildren(desktop, function(child){ return !d.isTopLevelWindow(child) && child.visible && !child.pinned; @@ -478,6 +481,8 @@ FocusScope { FocusHack { id: focusHack; } + FocusScope { id: nullFocus; } + Rectangle { id: focusDebugger; objectName: "focusDebugger" diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index 9c4313dc13..4f81f711e3 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -267,7 +267,7 @@ void OpenVrDisplayPlugin::unsuppressKeyboard() { return; } if (1 == _keyboardSupressionCount.fetch_sub(1)) { - enableOpenVrKeyboard(); + enableOpenVrKeyboard(_container); } } diff --git a/plugins/openvr/src/OpenVrHelpers.cpp b/plugins/openvr/src/OpenVrHelpers.cpp index 4f02c0384d..1bff0073f6 100644 --- a/plugins/openvr/src/OpenVrHelpers.cpp +++ b/plugins/openvr/src/OpenVrHelpers.cpp @@ -21,6 +21,9 @@ #include #include #include +#include +#include +#include "../../interface/src/Menu.h" Q_DECLARE_LOGGING_CATEGORY(displayplugins) Q_LOGGING_CATEGORY(displayplugins, "hifi.plugins.display") @@ -91,7 +94,7 @@ void releaseOpenVrSystem() { static char textArray[8192]; -static QMetaObject::Connection _focusConnection, _focusTextConnection; +static QMetaObject::Connection _focusConnection, _focusTextConnection, _overlayMenuConnection; extern bool _openVrDisplayActive; static vr::IVROverlay* _overlay { nullptr }; static QObject* _keyboardFocusObject { nullptr }; @@ -99,7 +102,8 @@ static QString _existingText; static Qt::InputMethodHints _currentHints; extern vr::TrackedDevicePose_t _trackedDevicePose[vr::k_unMaxTrackedDeviceCount]; static bool _keyboardShown { false }; -static const uint32_t SHOW_KEYBOARD_DELAY_MS = 100; +static bool _overlayRevealed { false }; +static const uint32_t SHOW_KEYBOARD_DELAY_MS = 400; void showOpenVrKeyboard(bool show = true) { if (!_overlay) { @@ -175,13 +179,25 @@ void finishOpenVrKeyboardInput() { static const QString DEBUG_FLAG("HIFI_DISABLE_STEAM_VR_KEYBOARD"); bool disableSteamVrKeyboard = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG); -void enableOpenVrKeyboard() { +void enableOpenVrKeyboard(PluginContainer* container) { if (disableSteamVrKeyboard) { return; } auto offscreenUi = DependencyManager::get(); _overlay = vr::VROverlay(); + + auto menu = container->getPrimaryMenu(); + auto action = menu->getActionForOption(MenuOption::Overlays); + + // When the overlays are revealed, suppress the keyboard from appearing on text focus for a tenth of a second. + _overlayMenuConnection = QObject::connect(action, &QAction::triggered, [action] { + if (action->isChecked()) { + _overlayRevealed = true; + QTimer::singleShot(100, [&] { _overlayRevealed = false; }); + } + }); + _focusConnection = QObject::connect(offscreenUi->getWindow(), &QQuickWindow::focusObjectChanged, [](QObject* object) { if (object != _keyboardFocusObject) { showOpenVrKeyboard(false); @@ -190,6 +206,11 @@ void enableOpenVrKeyboard() { _focusTextConnection = QObject::connect(offscreenUi.data(), &OffscreenUi::focusTextChanged, [](bool focusText) { if (_openVrDisplayActive) { + if (_overlayRevealed) { + // suppress at most one text focus event + _overlayRevealed = false; + return; + } showOpenVrKeyboard(focusText); } }); @@ -200,6 +221,7 @@ void disableOpenVrKeyboard() { if (disableSteamVrKeyboard) { return; } + QObject::disconnect(_overlayMenuConnection); QObject::disconnect(_focusTextConnection); QObject::disconnect(_focusConnection); } diff --git a/plugins/openvr/src/OpenVrHelpers.h b/plugins/openvr/src/OpenVrHelpers.h index 131db517ab..19c9cbfff5 100644 --- a/plugins/openvr/src/OpenVrHelpers.h +++ b/plugins/openvr/src/OpenVrHelpers.h @@ -13,6 +13,7 @@ #include #include +#include bool openVrSupported(); @@ -20,7 +21,7 @@ vr::IVRSystem* acquireOpenVrSystem(); void releaseOpenVrSystem(); void handleOpenVrEvents(); bool openVrQuitRequested(); -void enableOpenVrKeyboard(); +void enableOpenVrKeyboard(PluginContainer* container); void disableOpenVrKeyboard(); bool isOpenVrKeyboardShown(); diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 7f78ab8553..6c272d2a27 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -63,7 +63,7 @@ bool ViveControllerManager::activate() { } Q_ASSERT(_system); - enableOpenVrKeyboard(); + enableOpenVrKeyboard(_container); // OpenVR provides 3d mesh representations of the controllers // Disabled controller rendering code From 223f9bda2e6dcf92487ab4dac4cd3b44a762eef7 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Tue, 21 Jun 2016 17:03:49 -0700 Subject: [PATCH 89/93] gratuitous change to force things. --- scripts/system/controllers/handControllerPointer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/controllers/handControllerPointer.js b/scripts/system/controllers/handControllerPointer.js index c94325d3c0..374be0d1a1 100644 --- a/scripts/system/controllers/handControllerPointer.js +++ b/scripts/system/controllers/handControllerPointer.js @@ -1,6 +1,6 @@ "use strict"; /*jslint vars: true, plusplus: true*/ -/*globals Script, Overlays, Controller, Reticle, HMD, Camera, Entities, MyAvatar, Settings, Menu, ScriptDiscoveryService, Window, Vec3, Quat, print */ +/*globals Script, Overlays, Controller, Reticle, HMD, Camera, Entities, MyAvatar, Settings, Menu, ScriptDiscoveryService, Window, Vec3, Quat, print*/ // // handControllerPointer.js From a1844d8c19ab2d28ffc2870829bfc424c57aadb0 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Tue, 21 Jun 2016 17:36:41 -0700 Subject: [PATCH 90/93] Fix plugin include. (How did this build for me locally?) --- plugins/openvr/src/OpenVrHelpers.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/openvr/src/OpenVrHelpers.cpp b/plugins/openvr/src/OpenVrHelpers.cpp index 1bff0073f6..436cd7fc30 100644 --- a/plugins/openvr/src/OpenVrHelpers.cpp +++ b/plugins/openvr/src/OpenVrHelpers.cpp @@ -21,7 +21,7 @@ #include #include #include -#include +#include #include #include "../../interface/src/Menu.h" @@ -347,4 +347,4 @@ controller::Pose openVrControllerPoseToHandPose(bool isLeftHand, const mat4& mat result.velocity = linearVelocity + glm::cross(angularVelocity, position - extractTranslation(mat)); result.angularVelocity = angularVelocity; return result; -} \ No newline at end of file +} From e4dbb5e61a8ff9fd69f9a78039bb8ec95eb51f61 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 22 Jun 2016 09:57:38 -0700 Subject: [PATCH 91/93] Make mouse reticle be perpendicular to head. --- .../display-plugins/src/display-plugins/CompositorHelper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp index a17fe2fd49..032350a07c 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp @@ -425,7 +425,7 @@ glm::mat4 CompositorHelper::getReticleTransform(const glm::mat4& eyePose, const d = glm::normalize(overlaySurfacePoint); } reticlePosition = headPosition + (d * getReticleDepth()); - quat reticleOrientation = quat(vec3(-spherical.y, spherical.x, 0.0f)); + quat reticleOrientation = glm::quat_cast(_currentDisplayPlugin->getHeadPose()); vec3 reticleScale = vec3(Cursor::Manager::instance().getScale() * reticleSize * getReticleDepth()); return glm::inverse(eyePose) * createMatFromScaleQuatAndPos(reticleScale, reticleOrientation, reticlePosition); } else { From d3735322a5c9baa97312d4504069a476b18a16f3 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 22 Jun 2016 13:03:13 -0700 Subject: [PATCH 92/93] Whitespace. --- interface/src/scripting/ToolbarScriptingInterface.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/scripting/ToolbarScriptingInterface.h b/interface/src/scripting/ToolbarScriptingInterface.h index d3706cb6e1..9379284e55 100644 --- a/interface/src/scripting/ToolbarScriptingInterface.h +++ b/interface/src/scripting/ToolbarScriptingInterface.h @@ -20,7 +20,7 @@ class ToolbarProxy; class ToolbarScriptingInterface : public QObject, public Dependency { Q_OBJECT public: - Q_INVOKABLE QObject* getToolbar(const QString& toolbarId); + Q_INVOKABLE QObject* getToolbar(const QString& toolbarId); }; #endif // hifi_ToolbarScriptingInterface_h From 581d87d6536b45867ea751369d5a3bd534948014 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 22 Jun 2016 16:40:10 -0700 Subject: [PATCH 93/93] feedback re magic numbers and variable name. --- .../src/display-plugins/hmd/HmdDisplayPlugin.cpp | 11 ++++++----- plugins/openvr/src/OpenVrHelpers.cpp | 3 ++- scripts/system/controllers/handControllerGrab.js | 4 ++-- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index dbf264179e..f1aa1edc81 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -420,8 +420,9 @@ bool HmdDisplayPlugin::setHandLaser(uint32_t hands, HandLaserMode mode, const ve } void HmdDisplayPlugin::compositeExtra() { - std::array handLasers; - std::array renderHandPoses; + const int NUMBER_OF_HANDS = 2; + std::array handLasers; + std::array renderHandPoses; Transform uiModelTransform; withPresentThreadLock([&] { handLasers = _handLasers; @@ -443,9 +444,9 @@ void HmdDisplayPlugin::compositeExtra() { using namespace oglplus; useProgram(_laserProgram); _laserGeometry->Use(); - std::array handLaserModelMatrices; + std::array handLaserModelMatrices; - for (int i = 0; i < 2; ++i) { + for (int i = 0; i < NUMBER_OF_HANDS; ++i) { if (renderHandPoses[i] == identity) { continue; } @@ -485,7 +486,7 @@ void HmdDisplayPlugin::compositeExtra() { auto eyePose = _currentPresentFrameInfo.presentPose * getEyeToHeadTransform(eye); auto view = glm::inverse(eyePose); const auto& projection = _eyeProjections[eye]; - for (int i = 0; i < 2; ++i) { + for (int i = 0; i < NUMBER_OF_HANDS; ++i) { if (handLaserModelMatrices[i] == identity) { continue; } diff --git a/plugins/openvr/src/OpenVrHelpers.cpp b/plugins/openvr/src/OpenVrHelpers.cpp index 436cd7fc30..e71c8942d6 100644 --- a/plugins/openvr/src/OpenVrHelpers.cpp +++ b/plugins/openvr/src/OpenVrHelpers.cpp @@ -194,7 +194,8 @@ void enableOpenVrKeyboard(PluginContainer* container) { _overlayMenuConnection = QObject::connect(action, &QAction::triggered, [action] { if (action->isChecked()) { _overlayRevealed = true; - QTimer::singleShot(100, [&] { _overlayRevealed = false; }); + const int KEYBOARD_DELAY_MS = 100; + QTimer::singleShot(KEYBOARD_DELAY_MS, [&] { _overlayRevealed = false; }); } }); diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index 39d85f1224..7706132c58 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -877,8 +877,8 @@ function MyController(hand) { // find entities near the avatar that might be equipable. var entities = Entities.findEntities(MyAvatar.position, HOTSPOT_DRAW_DISTANCE); - var i, l = entities.length; - for (i = 0; i < l; i++) { + var i, length = entities.length; + for (i = 0; i < length; i++) { var grabProps = Entities.getEntityProperties(entities[i], GRABBABLE_PROPERTIES); // does this entity have an attach point? var wearableData = getEntityCustomData("wearable", entities[i], undefined);