diff --git a/interface/resources/qml/desktop/Desktop.qml b/interface/resources/qml/desktop/Desktop.qml index ca7226a6ab..579b4e7fd6 100644 --- a/interface/resources/qml/desktop/Desktop.qml +++ b/interface/resources/qml/desktop/Desktop.qml @@ -50,7 +50,22 @@ FocusScope { property bool desktopRoot: true // The VR version of the primary menu - property var rootMenu: Menu { objectName: "rootMenu" } + property var rootMenu: Menu { + objectName: "rootMenu" + + // for some reasons it is not possible to use just '({})' here as it gets empty when passed to TableRoot/DesktopRoot + property var exclusionGroupsByMenuItem : ListModel {} + + function addExclusionGroup(menuItem, exclusionGroup) + { + exclusionGroupsByMenuItem.append( + { + 'menuItem' : menuItem.toString(), + 'exclusionGroup' : exclusionGroup.toString() + } + ); + } + } // FIXME: Alpha gradients display as fuschia under QtQuick 2.5 on OSX/AMD // because shaders are 4.2, and do not include #version declarations. diff --git a/interface/resources/qml/hifi/tablet/TabletMenuItem.qml b/interface/resources/qml/hifi/tablet/TabletMenuItem.qml index 25f672e7a9..71e59e0d01 100644 --- a/interface/resources/qml/hifi/tablet/TabletMenuItem.qml +++ b/interface/resources/qml/hifi/tablet/TabletMenuItem.qml @@ -26,24 +26,48 @@ Item { visible: source.visible width: parent.width - CheckBox { + Item { id: check - // FIXME: Should use radio buttons if source.exclusiveGroup. + anchors { left: parent.left leftMargin: hifi.dimensions.menuPadding.x + 15 verticalCenter: label.verticalCenter } - width: 20 - visible: source.visible && source.type === 1 && source.checkable - checked: setChecked() - function setChecked() { - if (!source || source.type !== 1 || !source.checkable) { - return false; + + width: checkbox.visible ? checkbox.width : radiobutton.width + height: checkbox.visible ? checkbox.height : radiobutton.height + + CheckBox { + id: checkbox + // FIXME: Should use radio buttons if source.exclusiveGroup. + width: 20 + visible: source.visible && source.type === 1 && source.checkable && !source.exclusiveGroup + checked: setChecked() + function setChecked() { + if (!source || source.type !== 1 || !source.checkable) { + return false; + } + // FIXME this works for native QML menus but I don't think it will + // for proxied QML menus + return source.checked; + } + } + + RadioButton { + id: radiobutton + // FIXME: Should use radio buttons if source.exclusiveGroup. + width: 20 + visible: source.visible && source.type === 1 && source.checkable && source.exclusiveGroup + checked: setChecked() + function setChecked() { + if (!source || source.type !== 1 || !source.checkable) { + return false; + } + // FIXME this works for native QML menus but I don't think it will + // for proxied QML menus + return source.checked; } - // FIXME this works for native QML menus but I don't think it will - // for proxied QML menus - return source.checked; } } diff --git a/interface/resources/qml/hifi/tablet/TabletMenuStack.qml b/interface/resources/qml/hifi/tablet/TabletMenuStack.qml index 9076cd6c48..e7eefbc5e7 100644 --- a/interface/resources/qml/hifi/tablet/TabletMenuStack.qml +++ b/interface/resources/qml/hifi/tablet/TabletMenuStack.qml @@ -64,8 +64,10 @@ Item { d.pop(); } - function toModel(items) { + function toModel(items, newMenu) { var result = modelMaker.createObject(tabletMenu); + var exclusionGroups = {}; + for (var i = 0; i < items.length; ++i) { var item = items[i]; if (!item.visible) continue; @@ -77,6 +79,28 @@ Item { if (item.text !== "Users Online") { result.append({"name": item.text, "item": item}) } + + for(var j = 0; j < tabletMenu.rootMenu.exclusionGroupsByMenuItem.count; ++j) + { + var entry = tabletMenu.rootMenu.exclusionGroupsByMenuItem.get(j); + if(entry.menuItem == item.toString()) + { + var exclusionGroupId = entry.exclusionGroup; + console.debug('item exclusionGroupId: ', exclusionGroupId) + + if(!exclusionGroups[exclusionGroupId]) + { + exclusionGroups[exclusionGroupId] = exclusiveGroupMaker.createObject(newMenu); + console.debug('new exclusion group created: ', exclusionGroups[exclusionGroupId]) + } + + var exclusionGroup = exclusionGroups[exclusionGroupId]; + + item.exclusiveGroup = exclusionGroup + console.debug('item.exclusiveGroup: ', item.exclusiveGroup) + } + } + break; case MenuItemType.Separator: result.append({"name": "", "item": item}) @@ -133,10 +157,21 @@ Item { } } + property Component exclusiveGroupMaker: Component { + ExclusiveGroup { + } + } + function buildMenu(items) { - var model = toModel(items); // Menus must be childed to desktop for Z-ordering - var newMenu = menuViewMaker.createObject(tabletMenu, { model: model, isSubMenu: topMenu !== null }); + var newMenu = menuViewMaker.createObject(tabletMenu); + console.debug('newMenu created: ', newMenu) + + var model = toModel(items, newMenu); + + newMenu.model = model; + newMenu.isSubMenu = topMenu !== null; + pushMenu(newMenu); return newMenu; } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index f0d7fd0873..533b5a4bee 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1026,6 +1026,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo _glWidget->setFocusPolicy(Qt::StrongFocus); _glWidget->setFocus(); + if (cmdOptionExists(argc, constArgv, "--system-cursor")) { + _preferredCursor.set(Cursor::Manager::getIconName(Cursor::Icon::SYSTEM)); + } showCursor(Cursor::Manager::lookupIcon(_preferredCursor.get())); // enable mouse tracking; otherwise, we only get drag events @@ -4548,10 +4551,13 @@ void Application::updateMyAvatarLookAtPosition() { } } else { AvatarSharedPointer lookingAt = myAvatar->getLookAtTargetAvatar().lock(); - if (lookingAt && myAvatar.get() != lookingAt.get()) { + bool haveLookAtCandidate = lookingAt && myAvatar.get() != lookingAt.get(); + auto avatar = static_pointer_cast(lookingAt); + bool mutualLookAtSnappingEnabled = avatar && avatar->getLookAtSnappingEnabled() && myAvatar->getLookAtSnappingEnabled(); + if (haveLookAtCandidate && mutualLookAtSnappingEnabled) { // If I am looking at someone else, look directly at one of their eyes isLookingAtSomeone = true; - auto lookingAtHead = static_pointer_cast(lookingAt)->getHead(); + auto lookingAtHead = avatar->getHead(); const float MAXIMUM_FACE_ANGLE = 65.0f * RADIANS_PER_DEGREE; glm::vec3 lookingAtFaceOrientation = lookingAtHead->getFinalOrientationInWorldFrame() * IDENTITY_FORWARD; diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 2c4a515736..005d478411 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -56,6 +56,8 @@ Menu* Menu::getInstance() { return dynamic_cast(qApp->getWindow()->menuBar()); } +const char* exclusionGroupKey = "exclusionGroup"; + Menu::Menu() { auto dialogsManager = DependencyManager::get(); auto accountManager = DependencyManager::get(); @@ -222,32 +224,42 @@ Menu::Menu() { cameraModeGroup->setExclusive(true); // View > First Person - cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash( + auto firstPersonAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash( viewMenu, MenuOption::FirstPerson, Qt::CTRL | Qt::Key_F, true, qApp, SLOT(cameraMenuChanged()))); + firstPersonAction->setProperty(exclusionGroupKey, QVariant::fromValue(cameraModeGroup)); + // View > Third Person - cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash( + auto thirdPersonAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash( viewMenu, MenuOption::ThirdPerson, Qt::CTRL | Qt::Key_G, false, qApp, SLOT(cameraMenuChanged()))); + thirdPersonAction->setProperty(exclusionGroupKey, QVariant::fromValue(cameraModeGroup)); + // View > Mirror - cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash( + auto viewMirrorAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash( viewMenu, MenuOption::FullscreenMirror, Qt::CTRL | Qt::Key_H, false, qApp, SLOT(cameraMenuChanged()))); + viewMirrorAction->setProperty(exclusionGroupKey, QVariant::fromValue(cameraModeGroup)); + // View > Independent [advanced] - cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu, + auto viewIndependentAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::IndependentMode, 0, false, qApp, SLOT(cameraMenuChanged()), UNSPECIFIED_POSITION, "Advanced")); + viewIndependentAction->setProperty(exclusionGroupKey, QVariant::fromValue(cameraModeGroup)); + // View > Entity Camera [advanced] - cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu, + auto viewEntityCameraAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::CameraEntityMode, 0, false, qApp, SLOT(cameraMenuChanged()), UNSPECIFIED_POSITION, "Advanced")); + viewEntityCameraAction->setProperty(exclusionGroupKey, QVariant::fromValue(cameraModeGroup)); + viewMenu->addSeparator(); // View > Center Player In View @@ -532,6 +544,11 @@ Menu::Menu() { action = addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ShowOtherLookAtVectors, 0, false); connect(action, &QAction::triggered, [this]{ Avatar::setShowOtherLookAtVectors(isOptionChecked(MenuOption::ShowOtherLookAtVectors)); }); + action = addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::EnableLookAtSnapping, 0, true); + connect(action, &QAction::triggered, [this, avatar]{ + avatar->setProperty("lookAtSnappingEnabled", isOptionChecked(MenuOption::EnableLookAtSnapping)); + }); + addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::FixGaze, 0, false); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AnimDebugDrawDefaultPose, 0, false, avatar.get(), SLOT(setEnableDebugDrawDefaultPose(bool))); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 4e21cfa4ac..d77c87a6fc 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -177,6 +177,7 @@ namespace MenuOption { const QString ShowDSConnectTable = "Show Domain Connection Timing"; const QString ShowMyLookAtVectors = "Show My Eye Vectors"; const QString ShowOtherLookAtVectors = "Show Other Eye Vectors"; + const QString EnableLookAtSnapping = "Enable LookAt Snapping"; const QString ShowRealtimeEntityStats = "Show Realtime Entity Stats"; const QString StandingHMDSensorMode = "Standing HMD Sensor Mode"; const QString SimulateEyeTracking = "Simulate"; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 76535930bc..b69b8c79df 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -939,6 +939,9 @@ void MyAvatar::saveData() { settings.setValue("scale", _targetScale); + settings.setValue("yawSpeed", _yawSpeed); + settings.setValue("pitchSpeed", _pitchSpeed); + // only save the fullAvatarURL if it has not been overwritten on command line // (so the overrideURL is not valid), or it was overridden _and_ we specified // --replaceAvatarURL (so _saveAvatarOverrideUrl is true) @@ -1088,6 +1091,9 @@ void MyAvatar::loadData() { getHead()->setBasePitch(loadSetting(settings, "headPitch", 0.0f)); + _yawSpeed = loadSetting(settings, "yawSpeed", _yawSpeed); + _pitchSpeed = loadSetting(settings, "pitchSpeed", _pitchSpeed); + _prefOverrideAnimGraphUrl.set(QUrl(settings.value("animGraphURL", "").toString())); _fullAvatarURLFromPreferences = settings.value("fullAvatarURL", AvatarData::defaultFullAvatarModelUrl()).toUrl(); _fullAvatarModelName = settings.value("fullAvatarModelName", DEFAULT_FULL_AVATAR_MODEL_NAME).toString(); @@ -1210,6 +1216,15 @@ int MyAvatar::parseDataFromBuffer(const QByteArray& buffer) { return buffer.size(); } +ScriptAvatarData* MyAvatar::getTargetAvatar() const { + auto avatar = std::static_pointer_cast(_lookAtTargetAvatar.lock()); + if (avatar) { + return new ScriptAvatar(avatar); + } else { + return nullptr; + } +} + void MyAvatar::updateLookAtTargetAvatar() { // // Look at the avatar whose eyes are closest to the ray in direction of my avatar's head @@ -1238,9 +1253,8 @@ void MyAvatar::updateLookAtTargetAvatar() { if (angleTo < (smallestAngleTo * (isCurrentTarget ? KEEP_LOOKING_AT_CURRENT_ANGLE_FACTOR : 1.0f))) { _lookAtTargetAvatar = avatarPointer; _targetAvatarPosition = avatarPointer->getPosition(); - smallestAngleTo = angleTo; } - if (isLookingAtMe(avatar)) { + if (_lookAtSnappingEnabled && avatar->getLookAtSnappingEnabled() && isLookingAtMe(avatar)) { // Alter their gaze to look directly at my camera; this looks more natural than looking at my avatar's face. glm::vec3 lookAtPosition = avatar->getHead()->getLookAtPosition(); // A position, in world space, on my avatar. @@ -1257,14 +1271,19 @@ void MyAvatar::updateLookAtTargetAvatar() { ViewFrustum viewFrustum; qApp->copyViewFrustum(viewFrustum); + glm::vec3 viewPosition = viewFrustum.getPosition(); +#if DEBUG_ALWAYS_LOOKAT_EYES_NOT_CAMERA + viewPosition = (avatarLeftEye + avatarRightEye) / 2.0f; +#endif // scale gazeOffset by IPD, if wearing an HMD. if (qApp->isHMDMode()) { + glm::quat viewOrientation = viewFrustum.getOrientation(); glm::mat4 leftEye = qApp->getEyeOffset(Eye::Left); glm::mat4 rightEye = qApp->getEyeOffset(Eye::Right); glm::vec3 leftEyeHeadLocal = glm::vec3(leftEye[3]); glm::vec3 rightEyeHeadLocal = glm::vec3(rightEye[3]); - glm::vec3 humanLeftEye = viewFrustum.getPosition() + (viewFrustum.getOrientation() * leftEyeHeadLocal); - glm::vec3 humanRightEye = viewFrustum.getPosition() + (viewFrustum.getOrientation() * rightEyeHeadLocal); + glm::vec3 humanLeftEye = viewPosition + (viewOrientation * leftEyeHeadLocal); + glm::vec3 humanRightEye = viewPosition + (viewOrientation * rightEyeHeadLocal); auto hmdInterface = DependencyManager::get(); float ipdScale = hmdInterface->getIPDScale(); @@ -1278,7 +1297,7 @@ void MyAvatar::updateLookAtTargetAvatar() { } // And now we can finally add that offset to the camera. - glm::vec3 corrected = viewFrustum.getPosition() + gazeOffset; + glm::vec3 corrected = viewPosition + gazeOffset; avatar->getHead()->setCorrectedLookAtPosition(corrected); @@ -2526,6 +2545,7 @@ void MyAvatar::updateMotionBehaviorFromMenu() { _motionBehaviors &= ~AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED; } setCollisionsEnabled(menu->isOptionChecked(MenuOption::EnableAvatarCollisions)); + setProperty("lookAtSnappingEnabled", menu->isOptionChecked(MenuOption::EnableLookAtSnapping)); } void MyAvatar::setFlyingEnabled(bool enabled) { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 148a946ab4..65dcc12e7d 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -23,6 +23,7 @@ #include #include #include +#include #include "AtRestDetector.h" #include "MyCharacterController.h" @@ -138,6 +139,9 @@ class MyAvatar : public Avatar { Q_PROPERTY(bool characterControllerEnabled READ getCharacterControllerEnabled WRITE setCharacterControllerEnabled) Q_PROPERTY(bool useAdvancedMovementControls READ useAdvancedMovementControls WRITE setUseAdvancedMovementControls) + Q_PROPERTY(float yawSpeed MEMBER _yawSpeed) + Q_PROPERTY(float pitchSpeed MEMBER _pitchSpeed) + Q_PROPERTY(bool hmdRollControlEnabled READ getHMDRollControlEnabled WRITE setHMDRollControlEnabled) Q_PROPERTY(float hmdRollControlDeadZone READ getHMDRollControlDeadZone WRITE setHMDRollControlDeadZone) Q_PROPERTY(float hmdRollControlRate READ getHMDRollControlRate WRITE setHMDRollControlRate) @@ -390,6 +394,7 @@ public: Q_INVOKABLE glm::vec3 getEyePosition() const { return getHead()->getEyePosition(); } Q_INVOKABLE glm::vec3 getTargetAvatarPosition() const { return _targetAvatarPosition; } + Q_INVOKABLE ScriptAvatarData* getTargetAvatar() const; Q_INVOKABLE glm::vec3 getLeftHandPosition() const; Q_INVOKABLE glm::vec3 getRightHandPosition() const; diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 38ba60c977..7f4ebf39e1 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -557,8 +557,8 @@ void Avatar::postUpdate(float deltaTime) { if (isMyAvatar() ? showMyLookAtVectors : showOtherLookAtVectors) { const float EYE_RAY_LENGTH = 10.0; - const glm::vec4 BLUE(0.0f, 0.0f, 1.0f, 1.0f); - const glm::vec4 RED(1.0f, 0.0f, 0.0f, 1.0f); + const glm::vec4 BLUE(0.0f, 0.0f, _lookAtSnappingEnabled ? 1.0f : 0.25f, 1.0f); + const glm::vec4 RED(_lookAtSnappingEnabled ? 1.0f : 0.25f, 0.0f, 0.0f, 1.0f); int leftEyeJoint = getJointIndex("LeftEye"); glm::vec3 leftEyePosition; diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 477755adf8..1baf649e64 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -91,6 +91,7 @@ AvatarData::AvatarData() : _targetVelocity(0.0f), _density(DEFAULT_AVATAR_DENSITY) { + connect(this, &AvatarData::lookAtSnappingChanged, this, &AvatarData::markIdentityDataChanged); } AvatarData::~AvatarData() { @@ -1446,7 +1447,9 @@ void AvatarData::processAvatarIdentity(const QByteArray& identityData, bool& ide >> identity.displayName >> identity.sessionDisplayName >> identity.isReplicated - >> identity.avatarEntityData; + >> identity.avatarEntityData + >> identity.lookAtSnappingEnabled + ; // set the store identity sequence number to match the incoming identity _identitySequenceNumber = incomingSequenceNumber; @@ -1488,6 +1491,11 @@ void AvatarData::processAvatarIdentity(const QByteArray& identityData, bool& ide identityChanged = true; } + if (identity.lookAtSnappingEnabled != _lookAtSnappingEnabled) { + setProperty("lookAtSnappingEnabled", identity.lookAtSnappingEnabled); + identityChanged = true; + } + #ifdef WANT_DEBUG qCDebug(avatars) << __FUNCTION__ << "identity.uuid:" << identity.uuid @@ -1519,7 +1527,9 @@ QByteArray AvatarData::identityByteArray(bool setIsReplicated) const { << _displayName << getSessionDisplayNameForTransport() // depends on _sessionDisplayName << (_isReplicated || setIsReplicated) - << _avatarEntityData; + << _avatarEntityData + << _lookAtSnappingEnabled + ; }); return identityData; diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 78b1aa3633..b4c36dba70 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -353,6 +353,7 @@ class AvatarData : public QObject, public SpatiallyNestable { Q_PROPERTY(glm::vec3 position READ getPosition WRITE setPositionViaScript) Q_PROPERTY(float scale READ getTargetScale WRITE setTargetScale) + Q_PROPERTY(float density READ getDensity) Q_PROPERTY(glm::vec3 handPosition READ getHandPosition WRITE setHandPosition) Q_PROPERTY(float bodyYaw READ getBodyYaw WRITE setBodyYaw) Q_PROPERTY(float bodyPitch READ getBodyPitch WRITE setBodyPitch) @@ -374,6 +375,7 @@ class AvatarData : public QObject, public SpatiallyNestable { // sessionDisplayName is sanitized, defaulted version displayName that is defined by the AvatarMixer rather than by Interface clients. // The result is unique among all avatars present at the time. Q_PROPERTY(QString sessionDisplayName READ getSessionDisplayName WRITE setSessionDisplayName) + Q_PROPERTY(bool lookAtSnappingEnabled MEMBER _lookAtSnappingEnabled NOTIFY lookAtSnappingChanged) Q_PROPERTY(QString skeletonModelURL READ getSkeletonModelURLFromScript WRITE setSkeletonModelURLFromScript) Q_PROPERTY(QVector attachmentData READ getAttachmentData WRITE setAttachmentData) @@ -559,6 +561,7 @@ public: QString sessionDisplayName; bool isReplicated; AvatarEntityMap avatarEntityData; + bool lookAtSnappingEnabled; }; // identityChanged returns true if identity has changed, false otherwise. @@ -571,6 +574,7 @@ public: const QUrl& getSkeletonModelURL() const { return _skeletonModelURL; } const QString& getDisplayName() const { return _displayName; } const QString& getSessionDisplayName() const { return _sessionDisplayName; } + bool getLookAtSnappingEnabled() const { return _lookAtSnappingEnabled; } virtual void setSkeletonModelURL(const QUrl& skeletonModelURL); virtual void setDisplayName(const QString& displayName); @@ -662,6 +666,7 @@ public: signals: void displayNameChanged(); + void lookAtSnappingChanged(bool enabled); public slots: void sendAvatarDataPacket(); @@ -730,6 +735,7 @@ protected: QVector _attachmentData; QString _displayName; QString _sessionDisplayName { }; + bool _lookAtSnappingEnabled { true }; QHash _fstJointIndices; ///< 1-based, since zero is returned for missing keys QStringList _fstJointNames; ///< in order of depth-first traversal diff --git a/libraries/avatars/src/ScriptAvatarData.cpp b/libraries/avatars/src/ScriptAvatarData.cpp index 90ec7ec309..d0643d28f8 100644 --- a/libraries/avatars/src/ScriptAvatarData.cpp +++ b/libraries/avatars/src/ScriptAvatarData.cpp @@ -15,6 +15,7 @@ ScriptAvatarData::ScriptAvatarData(AvatarSharedPointer avatarData) : _avatarData(avatarData) { QObject::connect(avatarData.get(), &AvatarData::displayNameChanged, this, &ScriptAvatarData::displayNameChanged); + QObject::connect(avatarData.get(), &AvatarData::lookAtSnappingChanged, this, &ScriptAvatarData::lookAtSnappingChanged); } // @@ -161,6 +162,13 @@ bool ScriptAvatarData::getIsReplicated() const { } } +bool ScriptAvatarData::getLookAtSnappingEnabled() const { + if (AvatarSharedPointer sharedAvatarData = _avatarData.lock()) { + return sharedAvatarData->getLookAtSnappingEnabled(); + } else { + return false; + } +} // // IDENTIFIER PROPERTIES // END diff --git a/libraries/avatars/src/ScriptAvatarData.h b/libraries/avatars/src/ScriptAvatarData.h index 1b6944e01d..46dfb5325f 100644 --- a/libraries/avatars/src/ScriptAvatarData.h +++ b/libraries/avatars/src/ScriptAvatarData.h @@ -46,6 +46,7 @@ class ScriptAvatarData : public QObject { Q_PROPERTY(QString displayName READ getDisplayName NOTIFY displayNameChanged) Q_PROPERTY(QString sessionDisplayName READ getSessionDisplayName) Q_PROPERTY(bool isReplicated READ getIsReplicated) + Q_PROPERTY(bool lookAtSnappingEnabled READ getLookAtSnappingEnabled NOTIFY lookAtSnappingChanged) // // ATTACHMENT AND JOINT PROPERTIES @@ -97,6 +98,7 @@ public: QString getDisplayName() const; QString getSessionDisplayName() const; bool getIsReplicated() const; + bool getLookAtSnappingEnabled() const; // // ATTACHMENT AND JOINT PROPERTIES @@ -129,6 +131,7 @@ public: signals: void displayNameChanged(); + void lookAtSnappingChanged(bool enabled); public slots: glm::quat getAbsoluteJointRotationInObjectFrame(int index) const; diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp index 4dc8d3378c..f09b3d7109 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp @@ -437,7 +437,7 @@ glm::mat4 CompositorHelper::getReticleTransform(const glm::mat4& eyePose, const mousePosition -= 1.0; mousePosition.y *= -1.0f; - vec2 mouseSize = CURSOR_PIXEL_SIZE / canvasSize; + vec2 mouseSize = CURSOR_PIXEL_SIZE * Cursor::Manager::instance().getScale() / canvasSize; result = glm::scale(glm::translate(glm::mat4(), vec3(mousePosition, 0.0f)), vec3(mouseSize, 1.0f)); } return result; @@ -451,3 +451,13 @@ QVariant ReticleInterface::getPosition() const { void ReticleInterface::setPosition(QVariant position) { _compositor->setReticlePosition(vec2FromVariant(position)); } + +float ReticleInterface::getScale() const { + auto& cursorManager = Cursor::Manager::instance(); + return cursorManager.getScale(); +} + +void ReticleInterface::setScale(float scale) { + auto& cursorManager = Cursor::Manager::instance(); + cursorManager.setScale(scale); +} diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.h b/libraries/display-plugins/src/display-plugins/CompositorHelper.h index e6a32dcfb9..946229fc9b 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.h +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.h @@ -176,6 +176,7 @@ class ReticleInterface : public QObject { Q_PROPERTY(QVariant position READ getPosition WRITE setPosition) Q_PROPERTY(bool visible READ getVisible WRITE setVisible) Q_PROPERTY(float depth READ getDepth WRITE setDepth) + Q_PROPERTY(float scale READ getScale WRITE setScale) Q_PROPERTY(glm::vec2 maximumPosition READ getMaximumPosition) Q_PROPERTY(bool mouseCaptured READ isMouseCaptured) Q_PROPERTY(bool allowMouseCapture READ getAllowMouseCapture WRITE setAllowMouseCapture) @@ -197,6 +198,9 @@ public: Q_INVOKABLE float getDepth() { return _compositor->getReticleDepth(); } Q_INVOKABLE void setDepth(float depth) { _compositor->setReticleDepth(depth); } + Q_INVOKABLE float getScale() const; + Q_INVOKABLE void setScale(float scale); + Q_INVOKABLE QVariant getPosition() const; Q_INVOKABLE void setPosition(QVariant position); diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 241ccaf5d6..f44beb6d74 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -37,7 +37,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::AvatarData: case PacketType::BulkAvatarData: case PacketType::KillAvatar: - return static_cast(AvatarMixerPacketVersion::IsReplicatedInAvatarIdentity); + return static_cast(AvatarMixerPacketVersion::AvatarIdentityLookAtSnapping); case PacketType::MessagesData: return static_cast(MessageDataVersion::TextOrBinaryData); case PacketType::ICEServerHeartbeat: diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 3314e69d78..e8b74a74d5 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -288,7 +288,8 @@ enum class AvatarMixerPacketVersion : PacketVersion { AvatarIdentitySequenceId, MannequinDefaultAvatar, AvatarIdentitySequenceFront, - IsReplicatedInAvatarIdentity + IsReplicatedInAvatarIdentity, + AvatarIdentityLookAtSnapping, }; enum class DomainConnectRequestVersion : PacketVersion { diff --git a/libraries/ui/src/VrMenu.cpp b/libraries/ui/src/VrMenu.cpp index 3959e950e9..70daff944a 100644 --- a/libraries/ui/src/VrMenu.cpp +++ b/libraries/ui/src/VrMenu.cpp @@ -26,12 +26,14 @@ static unsigned int USER_DATA_ID = 0; // and qml object and inject the pointer into both objects class MenuUserData : public QObjectUserData { public: - MenuUserData(QAction* action, QObject* qmlObject) { + MenuUserData(QAction* action, QObject* qmlObject, QObject* qmlParent) { if (!USER_DATA_ID) { USER_DATA_ID = DependencyManager::get()->getMenuUserDataId(); } _action = action; _qml = qmlObject; + _qmlParent = qmlParent; + action->setUserData(USER_DATA_ID, this); qmlObject->setUserData(USER_DATA_ID, this); qmlObject->setObjectName(uuid.toString()); @@ -43,6 +45,41 @@ public: _shutdownConnection = QObject::connect(qApp, &QCoreApplication::aboutToQuit, [=] { QObject::disconnect(_changedConnection); }); + + class ExclusionGroupSetter : public QObject { + public: + ExclusionGroupSetter(QObject* from, QObject* to, QObject* qmlParent) : QObject(from), _from(from), _to(to), _qmlParent(qmlParent) { + _from->installEventFilter(this); + } + + ~ExclusionGroupSetter() { + _from->removeEventFilter(this); + } + protected: + virtual bool eventFilter(QObject* o, QEvent* e) override { + if (e->type() == QEvent::DynamicPropertyChange) { + QDynamicPropertyChangeEvent* dpc = static_cast(e); + if (dpc->propertyName() == "exclusionGroup") + { + // unfortunately Qt doesn't support passing dynamic properties between C++ / QML, so we have to use this ugly helper function + QMetaObject::invokeMethod(_qmlParent, + "addExclusionGroup", + Qt::DirectConnection, + Q_ARG(QVariant, QVariant::fromValue(_to)), + Q_ARG(QVariant, _from->property(dpc->propertyName()))); + } + } + + return QObject::eventFilter(o, e); + } + + private: + QObject* _from; + QObject* _to; + QObject* _qmlParent; + }; + + new ExclusionGroupSetter(action, qmlObject, qmlParent); } ~MenuUserData() { @@ -110,6 +147,7 @@ private: QMetaObject::Connection _changedConnection; QAction* _action { nullptr }; QObject* _qml { nullptr }; + QObject* _qmlParent{ nullptr }; }; @@ -157,16 +195,16 @@ void VrMenu::addMenu(QMenu* menu) { } // Bind the QML and Widget together - new MenuUserData(menu->menuAction(), result); + new MenuUserData(menu->menuAction(), result, qmlParent); } -void bindActionToQmlAction(QObject* qmlAction, QAction* action) { +void bindActionToQmlAction(QObject* qmlAction, QAction* action, QObject* qmlParent) { auto text = action->text(); if (text == "Login") { qDebug(uiLogging) << "Login action " << action; } - new MenuUserData(action, qmlAction); + new MenuUserData(action, qmlAction, qmlParent); QObject::connect(action, &QAction::toggled, [=](bool checked) { qmlAction->setProperty("checked", checked); }); @@ -195,7 +233,7 @@ void VrMenu::addAction(QMenu* menu, QAction* action) { QObject* result = reinterpret_cast(returnedValue); // returnedValue.value(); Q_ASSERT(result); // Bind the QML and Widget together - bindActionToQmlAction(result, action); + bindActionToQmlAction(result, action, _rootMenu); } void VrMenu::addSeparator(QMenu* menu) { @@ -231,7 +269,7 @@ void VrMenu::insertAction(QAction* before, QAction* action) { Q_ASSERT(invokeResult); QObject* result = reinterpret_cast(returnedValue); // returnedValue.value(); Q_ASSERT(result); - bindActionToQmlAction(result, action); + bindActionToQmlAction(result, action, _rootMenu); } class QQuickMenuBase; diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index 4b758f0add..7f24e7f634 100644 --- a/scripts/system/commerce/wallet.js +++ b/scripts/system/commerce/wallet.js @@ -14,7 +14,7 @@ /*global XXX */ (function () { // BEGIN LOCAL_SCOPE - + Script.include("/~/system/libraries/accountUtils.js"); // Function Name: onButtonClicked() // @@ -59,13 +59,7 @@ tablet.gotoHomeScreen(); break; case 'walletSetup_loginClicked': - if ((HMD.active && Settings.getValue("hmdTabletBecomesToolbar", false)) - || (!HMD.active && Settings.getValue("desktopTabletBecomesToolbar", true))) { - Menu.triggerOption("Login / Sign Up"); - tablet.gotoHomeScreen(); - } else { - tablet.loadQMLOnTop("../../../dialogs/TabletLoginDialog.qml"); - } + openLoginWindow(); break; default: print('Unrecognized message from QML:', JSON.stringify(message)); diff --git a/scripts/system/libraries/accountUtils.js b/scripts/system/libraries/accountUtils.js new file mode 100644 index 0000000000..6df0aa3a87 --- /dev/null +++ b/scripts/system/libraries/accountUtils.js @@ -0,0 +1,16 @@ +// +// accountUtils.js +// scripts/system/libraries/libraries +// +// Copyright 2017 High Fidelity, Inc. +// + +openLoginWindow = function openLoginWindow() { + if ((HMD.active && Settings.getValue("hmdTabletBecomesToolbar", false)) + || (!HMD.active && Settings.getValue("desktopTabletBecomesToolbar", true))) { + Menu.triggerOption("Login / Sign Up"); + } else { + tablet.loadQMLOnTop("../../dialogs/TabletLoginDialog.qml"); + HMD.openTablet(); + } +}; diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index df5ed45fed..e5c60af77b 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -10,7 +10,8 @@ /* globals Tablet, Script, HMD, Settings, DialogsManager, Menu, Reticle, OverlayWebWindow, Desktop, Account, MyAvatar, Snapshot */ /* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ -(function() { // BEGIN LOCAL_SCOPE +(function () { // BEGIN LOCAL_SCOPE +Script.include("/~/system/libraries/accountUtils.js"); var SNAPSHOT_DELAY = 500; // 500ms var FINISH_SOUND_DELAY = 350; @@ -52,15 +53,7 @@ try { print('Failed to resolve request api, error: ' + err); } -function openLoginWindow() { - if ((HMD.active && Settings.getValue("hmdTabletBecomesToolbar", false)) - || (!HMD.active && Settings.getValue("desktopTabletBecomesToolbar", true))) { - Menu.triggerOption("Login / Sign Up"); - } else { - tablet.loadQMLOnTop("../../dialogs/TabletLoginDialog.qml"); - HMD.openTablet(); - } -} + function removeFromStoryIDsToMaybeDelete(story_id) { storyIDsToMaybeDelete.splice(storyIDsToMaybeDelete.indexOf(story_id), 1);