From a10b157affddd7bba5a294ffa63ef310a953b6a2 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 4 Apr 2017 17:26:00 -0700 Subject: [PATCH 01/76] First pass at having an explicit Hips IK target. Also, AnimManipulator nodes support setting position and rotation on a single joint. --- .../resources/avatar/avatar-animation.json | 23 ++- interface/src/avatar/MyAvatar.h | 3 + interface/src/avatar/SkeletonModel.cpp | 4 + .../animation/src/AnimInverseKinematics.cpp | 143 ++++++------------ libraries/animation/src/AnimManipulator.cpp | 95 ++++++++---- libraries/animation/src/AnimManipulator.h | 17 ++- libraries/animation/src/AnimNodeLoader.cpp | 33 ++-- libraries/animation/src/AnimOverlay.cpp | 8 + libraries/animation/src/AnimOverlay.h | 2 + libraries/animation/src/AnimVariant.h | 9 ++ libraries/animation/src/IKTarget.h | 3 +- libraries/animation/src/Rig.cpp | 11 +- libraries/animation/src/Rig.h | 2 + 13 files changed, 205 insertions(+), 148 deletions(-) diff --git a/interface/resources/avatar/avatar-animation.json b/interface/resources/avatar/avatar-animation.json index 975f01855d..daa69b95f7 100644 --- a/interface/resources/avatar/avatar-animation.json +++ b/interface/resources/avatar/avatar-animation.json @@ -50,6 +50,12 @@ "type": "inverseKinematics", "data": { "targets": [ + { + "jointName": "Hips", + "positionVar": "hipsPosition", + "rotationVar": "hipsRotation", + "typeVar": "hipsType" + }, { "jointName": "RightHand", "positionVar": "rightHandPosition", @@ -91,20 +97,27 @@ "children": [] }, { - "id": "manipulatorOverlay", + "id": "hipsManipulatorOverlay", "type": "overlay", "data": { - "alpha": 1.0, - "boneSet": "spineOnly" + "alpha": 0.0, + "boneSet": "hipsOnly" }, "children": [ { - "id": "spineLean", + "id": "hipsManipulator", "type": "manipulator", "data": { "alpha": 0.0, + "alphaVar": "hipsManipulatorAlpha", "joints": [ - { "type": "absoluteRotation", "jointName": "Spine", "var": "lean" } + { + "jointName": "Hips", + "rotationType": "absolute", + "translationType": "absolute", + "rotationVar": "hipsManipulatorRotation", + "translationVar": "hipsManipulatorPosition" + } ] }, "children": [] diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 097d3a1059..b5bea23aa3 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -553,9 +553,12 @@ private: void setVisibleInSceneIfReady(Model* model, render::ScenePointer scene, bool visiblity); + // AJT: FIX ME... reorder this +public: // derive avatar body position and orientation from the current HMD Sensor location. // results are in HMD frame glm::mat4 deriveBodyFromHMDSensor() const; +private: virtual void updatePalms() override {} void lateUpdatePalms(); diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 0c11fa456d..e26c339fb8 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -119,7 +119,11 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { headParams.rigHeadPosition = extractTranslation(rigHMDMat); headParams.rigHeadOrientation = extractRotation(rigHMDMat); headParams.worldHeadOrientation = extractRotation(worldHMDMat); + + headParams.hipsMatrix = worldToRig * myAvatar->getSensorToWorldMatrix() * myAvatar->deriveBodyFromHMDSensor(); + headParams.hipsEnabled = true; } else { + headParams.hipsEnabled = false; headParams.isInHMD = false; // We don't have a valid localHeadPosition. diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 2c9376d591..a27fd01b3c 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -87,6 +87,7 @@ void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std:: // build a list of valid targets from _targetVarVec and animVars _maxTargetIndex = -1; bool removeUnfoundJoints = false; + for (auto& targetVar : _targetVarVec) { if (targetVar.jointIndex == -1) { // this targetVar hasn't been validated yet... @@ -105,9 +106,8 @@ void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std:: AnimPose defaultPose = _skeleton->getAbsolutePose(targetVar.jointIndex, underPoses); glm::quat rotation = animVars.lookupRigToGeometry(targetVar.rotationVar, defaultPose.rot()); glm::vec3 translation = animVars.lookupRigToGeometry(targetVar.positionVar, defaultPose.trans()); - if (target.getType() == IKTarget::Type::HipsRelativeRotationAndPosition) { - translation += _hipsOffset; - } + AnimPose absPose(glm::vec3(1.0f), rotation, translation); + target.setPose(rotation, translation); target.setIndex(targetVar.jointIndex); targets.push_back(target); @@ -441,26 +441,6 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars computeTargets(animVars, targets, underPoses); } - // debug render ik targets - if (context.getEnableDebugDrawIKTargets()) { - const vec4 WHITE(1.0f); - glm::mat4 rigToAvatarMat = createMatFromQuatAndPos(Quaternions::Y_180, glm::vec3()); - - for (auto& target : targets) { - glm::mat4 geomTargetMat = createMatFromQuatAndPos(target.getRotation(), target.getTranslation()); - glm::mat4 avatarTargetMat = rigToAvatarMat * context.getGeometryToRigMatrix() * geomTargetMat; - - QString name = QString("ikTarget%1").arg(target.getIndex()); - DebugDraw::getInstance().addMyAvatarMarker(name, glmExtractRotation(avatarTargetMat), extractTranslation(avatarTargetMat), WHITE); - } - } else if (context.getEnableDebugDrawIKTargets() != _previousEnableDebugIKTargets) { - // remove markers if they were added last frame. - for (auto& target : targets) { - QString name = QString("ikTarget%1").arg(target.getIndex()); - DebugDraw::getInstance().removeMyAvatarMarker(name); - } - } - _previousEnableDebugIKTargets = context.getEnableDebugDrawIKTargets(); if (targets.empty()) { @@ -478,25 +458,52 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars { PROFILE_RANGE_EX(simulation_animation, "ik/shiftHips", 0xffff00ff, 0); - // shift hips according to the _hipsOffset from the previous frame - float offsetLength = glm::length(_hipsOffset); - const float MIN_HIPS_OFFSET_LENGTH = 0.03f; - if (offsetLength > MIN_HIPS_OFFSET_LENGTH && _hipsIndex >= 0) { - // but only if offset is long enough - float scaleFactor = ((offsetLength - MIN_HIPS_OFFSET_LENGTH) / offsetLength); - if (_hipsParentIndex == -1) { - // the hips are the root so _hipsOffset is in the correct frame - _relativePoses[_hipsIndex].trans() = underPoses[_hipsIndex].trans() + scaleFactor * _hipsOffset; - } else { - // the hips are NOT the root so we need to transform _hipsOffset into hips local-frame - glm::quat hipsFrameRotation = _relativePoses[_hipsParentIndex].rot(); - int index = _skeleton->getParentIndex(_hipsParentIndex); - while (index != -1) { - hipsFrameRotation *= _relativePoses[index].rot(); - index = _skeleton->getParentIndex(index); + // AJT: TODO only need abs poses below hips. + AnimPoseVec absolutePoses; + absolutePoses.resize(_relativePoses.size()); + computeAbsolutePoses(absolutePoses); + + for (auto& target: targets) { + if (target.getType() == IKTarget::Type::RotationAndPosition && target.getIndex() == _hipsIndex) { + AnimPose absPose = target.getPose(); + int parentIndex = _skeleton->getParentIndex(target.getIndex()); + if (parentIndex != -1) { + _relativePoses[_hipsIndex] = absolutePoses[parentIndex].inverse() * absPose; + } else { + _relativePoses[_hipsIndex] = absPose; } - _relativePoses[_hipsIndex].trans() = underPoses[_hipsIndex].trans() - + glm::inverse(glm::normalize(hipsFrameRotation)) * (scaleFactor * _hipsOffset); + } + } + + for (auto& target: targets) { + if (target.getType() == IKTarget::Type::HipsRelativeRotationAndPosition) { + AnimPose pose = target.getPose(); + pose.trans() = pose.trans() + (_relativePoses[_hipsIndex].trans() - underPoses[_hipsIndex].trans()); + target.setPose(pose.rot(), pose.trans()); + } + } + } + + { + PROFILE_RANGE_EX(simulation_animation, "ik/debugDraw", 0xffff00ff, 0); + + // debug render ik targets + if (context.getEnableDebugDrawIKTargets()) { + const vec4 WHITE(1.0f); + glm::mat4 rigToAvatarMat = createMatFromQuatAndPos(Quaternions::Y_180, glm::vec3()); + + for (auto& target : targets) { + glm::mat4 geomTargetMat = createMatFromQuatAndPos(target.getRotation(), target.getTranslation()); + glm::mat4 avatarTargetMat = rigToAvatarMat * context.getGeometryToRigMatrix() * geomTargetMat; + + QString name = QString("ikTarget%1").arg(target.getIndex()); + DebugDraw::getInstance().addMyAvatarMarker(name, glmExtractRotation(avatarTargetMat), extractTranslation(avatarTargetMat), WHITE); + } + } else if (context.getEnableDebugDrawIKTargets() != _previousEnableDebugIKTargets) { + // remove markers if they were added last frame. + for (auto& target : targets) { + QString name = QString("ikTarget%1").arg(target.getIndex()); + DebugDraw::getInstance().removeMyAvatarMarker(name); } } } @@ -505,60 +512,6 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars PROFILE_RANGE_EX(simulation_animation, "ik/ccd", 0xffff00ff, 0); solveWithCyclicCoordinateDescent(targets); } - - { - PROFILE_RANGE_EX(simulation_animation, "ik/measureHipsOffset", 0xffff00ff, 0); - - // measure new _hipsOffset for next frame - // by looking for discrepancies between where a targeted endEffector is - // and where it wants to be (after IK solutions are done) - glm::vec3 newHipsOffset = Vectors::ZERO; - for (auto& target: targets) { - int targetIndex = target.getIndex(); - if (targetIndex == _headIndex && _headIndex != -1) { - // special handling for headTarget - if (target.getType() == IKTarget::Type::RotationOnly) { - // we want to shift the hips to bring the underPose closer - // to where the head happens to be (overpose) - glm::vec3 under = _skeleton->getAbsolutePose(_headIndex, underPoses).trans(); - glm::vec3 actual = _skeleton->getAbsolutePose(_headIndex, _relativePoses).trans(); - const float HEAD_OFFSET_SLAVE_FACTOR = 0.65f; - newHipsOffset += HEAD_OFFSET_SLAVE_FACTOR * (actual - under); - } else if (target.getType() == IKTarget::Type::HmdHead) { - // we want to shift the hips to bring the head to its designated position - glm::vec3 actual = _skeleton->getAbsolutePose(_headIndex, _relativePoses).trans(); - _hipsOffset += target.getTranslation() - actual; - // and ignore all other targets - newHipsOffset = _hipsOffset; - break; - } else if (target.getType() == IKTarget::Type::RotationAndPosition) { - glm::vec3 actualPosition = _skeleton->getAbsolutePose(targetIndex, _relativePoses).trans(); - glm::vec3 targetPosition = target.getTranslation(); - newHipsOffset += targetPosition - actualPosition; - - // Add downward pressure on the hips - newHipsOffset *= 0.95f; - newHipsOffset -= 1.0f; - } - } else if (target.getType() == IKTarget::Type::RotationAndPosition) { - glm::vec3 actualPosition = _skeleton->getAbsolutePose(targetIndex, _relativePoses).trans(); - glm::vec3 targetPosition = target.getTranslation(); - newHipsOffset += targetPosition - actualPosition; - } - } - - // smooth transitions by relaxing _hipsOffset toward the new value - const float HIPS_OFFSET_SLAVE_TIMESCALE = 0.10f; - float tau = dt < HIPS_OFFSET_SLAVE_TIMESCALE ? dt / HIPS_OFFSET_SLAVE_TIMESCALE : 1.0f; - _hipsOffset += (newHipsOffset - _hipsOffset) * tau; - - // clamp the hips offset - float hipsOffsetLength = glm::length(_hipsOffset); - if (hipsOffsetLength > _maxHipsOffsetLength) { - _hipsOffset *= _maxHipsOffsetLength / hipsOffsetLength; - } - - } } } return _relativePoses; diff --git a/libraries/animation/src/AnimManipulator.cpp b/libraries/animation/src/AnimManipulator.cpp index 111501898a..070949ab3b 100644 --- a/libraries/animation/src/AnimManipulator.cpp +++ b/libraries/animation/src/AnimManipulator.cpp @@ -12,6 +12,16 @@ #include "AnimUtil.h" #include "AnimationLogging.h" +AnimManipulator::JointVar::JointVar(const QString& jointNameIn, Type rotationTypeIn, Type translationTypeIn, + const QString& rotationVarIn, const QString& translationVarIn) : + jointName(jointNameIn), + rotationType(rotationTypeIn), + translationType(translationTypeIn), + rotationVar(rotationVarIn), + translationVar(translationVarIn), + jointIndex(-1), + hasPerformedJointLookup(false) {} + AnimManipulator::AnimManipulator(const QString& id, float alpha) : AnimNode(AnimNode::Type::Manipulator, id), _alpha(alpha) { @@ -36,7 +46,10 @@ const AnimPoseVec& AnimManipulator::overlay(const AnimVariantMap& animVars, cons } for (auto& jointVar : _jointVars) { + if (!jointVar.hasPerformedJointLookup) { + + // map from joint name to joint index and cache the result. jointVar.jointIndex = _skeleton->nameToJointIndex(jointVar.jointName); if (jointVar.jointIndex < 0) { qCWarning(animation) << "AnimManipulator could not find jointName" << jointVar.jointName << "in skeleton"; @@ -100,34 +113,62 @@ AnimPose AnimManipulator::computeRelativePoseFromJointVar(const AnimVariantMap& AnimPose defaultAbsPose = _skeleton->getAbsolutePose(jointVar.jointIndex, underPoses); - if (jointVar.type == JointVar::Type::AbsoluteRotation || jointVar.type == JointVar::Type::AbsolutePosition) { + // compute relative translation + glm::vec3 relTrans; + switch (jointVar.translationType) { + case JointVar::Type::Absolute: { + glm::vec3 absTrans = animVars.lookupRigToGeometry(jointVar.translationVar, defaultAbsPose.trans()); - if (jointVar.type == JointVar::Type::AbsoluteRotation) { - defaultAbsPose.rot() = animVars.lookupRigToGeometry(jointVar.var, defaultAbsPose.rot()); - } else if (jointVar.type == JointVar::Type::AbsolutePosition) { - defaultAbsPose.trans() = animVars.lookupRigToGeometry(jointVar.var, defaultAbsPose.trans()); + // convert to from absolute to relative. + AnimPose parentAbsPose; + int parentIndex = _skeleton->getParentIndex(jointVar.jointIndex); + if (parentIndex >= 0) { + parentAbsPose = _skeleton->getAbsolutePose(parentIndex, underPoses); + } + + // convert from absolute to relative + relTrans = transformPoint(parentAbsPose.inverse(), absTrans); + break; } - - // because jointVar is absolute, we must use an absolute parent frame to convert into a relative pose. - AnimPose parentAbsPose = AnimPose::identity; - int parentIndex = _skeleton->getParentIndex(jointVar.jointIndex); - if (parentIndex >= 0) { - parentAbsPose = _skeleton->getAbsolutePose(parentIndex, underPoses); - } - - // convert from absolute to relative - return parentAbsPose.inverse() * defaultAbsPose; - - } else { - - // override the default rel pose - AnimPose relPose = defaultRelPose; - if (jointVar.type == JointVar::Type::RelativeRotation) { - relPose.rot() = animVars.lookupRigToGeometry(jointVar.var, defaultRelPose.rot()); - } else if (jointVar.type == JointVar::Type::RelativePosition) { - relPose.trans() = animVars.lookupRigToGeometry(jointVar.var, defaultRelPose.trans()); - } - - return relPose; + case JointVar::Type::Relative: + relTrans = animVars.lookupRigToGeometryVector(jointVar.translationVar, defaultRelPose.trans()); + break; + case JointVar::Type::UnderPose: + relTrans = underPoses[jointVar.jointIndex].trans(); + break; + case JointVar::Type::Default: + default: + relTrans = defaultRelPose.trans(); + break; } + + glm::quat relRot; + switch (jointVar.rotationType) { + case JointVar::Type::Absolute: { + glm::quat absRot = animVars.lookupRigToGeometry(jointVar.translationVar, defaultAbsPose.rot()); + + // convert to from absolute to relative. + AnimPose parentAbsPose; + int parentIndex = _skeleton->getParentIndex(jointVar.jointIndex); + if (parentIndex >= 0) { + parentAbsPose = _skeleton->getAbsolutePose(parentIndex, underPoses); + } + + // convert from absolute to relative + relRot = glm::inverse(parentAbsPose.rot()) * absRot; + break; + } + case JointVar::Type::Relative: + relRot = animVars.lookupRigToGeometry(jointVar.translationVar, defaultRelPose.rot()); + break; + case JointVar::Type::UnderPose: + relRot = underPoses[jointVar.jointIndex].rot(); + break; + case JointVar::Type::Default: + default: + relRot = defaultRelPose.rot(); + break; + } + + return AnimPose(glm::vec3(1), relRot, relTrans); } diff --git a/libraries/animation/src/AnimManipulator.h b/libraries/animation/src/AnimManipulator.h index 26f50a7dd9..1134f75da9 100644 --- a/libraries/animation/src/AnimManipulator.h +++ b/libraries/animation/src/AnimManipulator.h @@ -31,17 +31,20 @@ public: struct JointVar { enum class Type { - AbsoluteRotation = 0, - AbsolutePosition, - RelativeRotation, - RelativePosition, + Absolute, + Relative, + UnderPose, + Default, NumTypes }; - JointVar(const QString& varIn, const QString& jointNameIn, Type typeIn) : var(varIn), jointName(jointNameIn), type(typeIn), jointIndex(-1), hasPerformedJointLookup(false) {} - QString var = ""; + JointVar(const QString& jointNameIn, Type rotationType, Type translationType, const QString& rotationVarIn, const QString& translationVarIn); QString jointName = ""; - Type type = Type::AbsoluteRotation; + Type rotationType = Type::Absolute; + Type translationType = Type::Absolute; + QString rotationVar = ""; + QString translationVar = ""; + int jointIndex = -1; bool hasPerformedJointLookup = false; bool isRelative = false; diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index 876913fc58..bda4541f36 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -79,10 +79,10 @@ static AnimStateMachine::InterpType stringToInterpType(const QString& str) { static const char* animManipulatorJointVarTypeToString(AnimManipulator::JointVar::Type type) { switch (type) { - case AnimManipulator::JointVar::Type::AbsoluteRotation: return "absoluteRotation"; - case AnimManipulator::JointVar::Type::AbsolutePosition: return "absolutePosition"; - case AnimManipulator::JointVar::Type::RelativeRotation: return "relativeRotation"; - case AnimManipulator::JointVar::Type::RelativePosition: return "relativePosition"; + case AnimManipulator::JointVar::Type::Absolute: return "absolute"; + case AnimManipulator::JointVar::Type::Relative: return "relative"; + case AnimManipulator::JointVar::Type::UnderPose: return "underPose"; + case AnimManipulator::JointVar::Type::Default: return "default"; case AnimManipulator::JointVar::Type::NumTypes: return nullptr; }; return nullptr; @@ -339,7 +339,8 @@ static const char* boneSetStrings[AnimOverlay::NumBoneSets] = { "spineOnly", "empty", "leftHand", - "rightHand" + "rightHand", + "hipsOnly" }; static AnimOverlay::BoneSet stringToBoneSetEnum(const QString& str) { @@ -406,17 +407,25 @@ static AnimNode::Pointer loadManipulatorNode(const QJsonObject& jsonObj, const Q } auto jointObj = jointValue.toObject(); - READ_STRING(type, jointObj, id, jsonUrl, nullptr); READ_STRING(jointName, jointObj, id, jsonUrl, nullptr); - READ_STRING(var, jointObj, id, jsonUrl, nullptr); + READ_STRING(rotationType, jointObj, id, jsonUrl, nullptr); + READ_STRING(translationType, jointObj, id, jsonUrl, nullptr); + READ_STRING(rotationVar, jointObj, id, jsonUrl, nullptr); + READ_STRING(translationVar, jointObj, id, jsonUrl, nullptr); - AnimManipulator::JointVar::Type jointVarType = stringToAnimManipulatorJointVarType(type); - if (jointVarType == AnimManipulator::JointVar::Type::NumTypes) { - qCCritical(animation) << "AnimNodeLoader, bad type in \"joints\", id =" << id << ", url =" << jsonUrl.toDisplayString(); - return nullptr; + AnimManipulator::JointVar::Type jointVarRotationType = stringToAnimManipulatorJointVarType(rotationType); + if (jointVarRotationType == AnimManipulator::JointVar::Type::NumTypes) { + qCWarning(animation) << "AnimNodeLoader, bad rotationType in \"joints\", id =" << id << ", url =" << jsonUrl.toDisplayString(); + jointVarRotationType = AnimManipulator::JointVar::Type::Default; } - AnimManipulator::JointVar jointVar(var, jointName, jointVarType); + AnimManipulator::JointVar::Type jointVarTranslationType = stringToAnimManipulatorJointVarType(translationType); + if (jointVarTranslationType == AnimManipulator::JointVar::Type::NumTypes) { + qCWarning(animation) << "AnimNodeLoader, bad translationType in \"joints\", id =" << id << ", url =" << jsonUrl.toDisplayString(); + jointVarTranslationType = AnimManipulator::JointVar::Type::Default; + } + + AnimManipulator::JointVar jointVar(jointName, jointVarRotationType, jointVarTranslationType, rotationVar, translationVar); node->addJointVar(jointVar); }; diff --git a/libraries/animation/src/AnimOverlay.cpp b/libraries/animation/src/AnimOverlay.cpp index dbc635af66..e086413dde 100644 --- a/libraries/animation/src/AnimOverlay.cpp +++ b/libraries/animation/src/AnimOverlay.cpp @@ -34,6 +34,7 @@ void AnimOverlay::buildBoneSet(BoneSet boneSet) { case SpineOnlyBoneSet: buildSpineOnlyBoneSet(); break; case LeftHandBoneSet: buildLeftHandBoneSet(); break; case RightHandBoneSet: buildRightHandBoneSet(); break; + case HipsOnlyBoneSet: buildHipsOnlyBoneSet(); break; default: case EmptyBoneSet: buildEmptyBoneSet(); break; } @@ -188,6 +189,13 @@ void AnimOverlay::buildRightHandBoneSet() { }); } +void AnimOverlay::buildHipsOnlyBoneSet() { + assert(_skeleton); + buildEmptyBoneSet(); + int hipsJoint = _skeleton->nameToJointIndex("Hips"); + _boneSetVec[hipsJoint] = 1.0f; +} + // for AnimDebugDraw rendering const AnimPoseVec& AnimOverlay::getPosesInternal() const { return _poses; diff --git a/libraries/animation/src/AnimOverlay.h b/libraries/animation/src/AnimOverlay.h index 2f34c07309..ed9439feb7 100644 --- a/libraries/animation/src/AnimOverlay.h +++ b/libraries/animation/src/AnimOverlay.h @@ -37,6 +37,7 @@ public: EmptyBoneSet, LeftHandBoneSet, RightHandBoneSet, + HipsOnlyBoneSet, NumBoneSets }; @@ -75,6 +76,7 @@ public: void buildEmptyBoneSet(); void buildLeftHandBoneSet(); void buildRightHandBoneSet(); + void buildHipsOnlyBoneSet(); // no copies AnimOverlay(const AnimOverlay&) = delete; diff --git a/libraries/animation/src/AnimVariant.h b/libraries/animation/src/AnimVariant.h index 3466013ff6..d383b5abb8 100644 --- a/libraries/animation/src/AnimVariant.h +++ b/libraries/animation/src/AnimVariant.h @@ -165,6 +165,15 @@ public: } } + glm::vec3 lookupRigToGeometryVector(const QString& key, const glm::vec3& defaultValue) const { + if (key.isEmpty()) { + return defaultValue; + } else { + auto iter = _map.find(key); + return iter != _map.end() ? transformVectorFast(_rigToGeometryMat, iter->second.getVec3()) : defaultValue; + } + } + const glm::quat& lookupRaw(const QString& key, const glm::quat& defaultValue) const { if (key.isEmpty()) { return defaultValue; diff --git a/libraries/animation/src/IKTarget.h b/libraries/animation/src/IKTarget.h index 9ea34a6165..acb01d9861 100644 --- a/libraries/animation/src/IKTarget.h +++ b/libraries/animation/src/IKTarget.h @@ -21,13 +21,14 @@ public: RotationOnly, HmdHead, HipsRelativeRotationAndPosition, - Unknown, + Unknown }; IKTarget() {} const glm::vec3& getTranslation() const { return _pose.trans(); } const glm::quat& getRotation() const { return _pose.rot(); } + const AnimPose& getPose() const { return _pose; } int getIndex() const { return _index; } Type getType() const { return _type; } diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index fb0867e2de..adc40d6e81 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1024,6 +1024,15 @@ void Rig::updateFromHeadParameters(const HeadParameters& params, float dt) { _animVars.set("isTalking", params.isTalking); _animVars.set("notIsTalking", !params.isTalking); + + // AJT: + if (params.hipsEnabled) { + _animVars.set("hipsType", (int)IKTarget::Type::RotationAndPosition); + _animVars.set("hipsPosition", extractTranslation(params.hipsMatrix)); + _animVars.set("hipsRotation", glmExtractRotation(params.hipsMatrix) * Quaternions::Y_180); + } else { + _animVars.set("hipsType", (int)IKTarget::Type::Unknown); + } } void Rig::updateFromEyeParameters(const EyeParameters& params) { @@ -1094,7 +1103,7 @@ void Rig::updateNeckJoint(int index, const HeadParameters& params) { _animVars.set("headPosition", headPos); _animVars.set("headRotation", headRot); - _animVars.set("headType", (int)IKTarget::Type::HmdHead); + _animVars.set("headType", (int)IKTarget::Type::RotationAndPosition); _animVars.set("neckPosition", neckPos); _animVars.set("neckRotation", neckRot); _animVars.set("neckType", (int)IKTarget::Type::Unknown); // 'Unknown' disables the target diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 2cd20c2704..89f0d624f9 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -45,6 +45,8 @@ public: glm::quat worldHeadOrientation = glm::quat(); // world space (-z forward) glm::quat rigHeadOrientation = glm::quat(); // rig space (-z forward) glm::vec3 rigHeadPosition = glm::vec3(); // rig space + glm::mat4 hipsMatrix = glm::mat4(); // rig space + bool hipsEnabled = false; bool isInHMD = false; int neckJointIndex = -1; bool isTalking = false; From adaf7dda7c3b1105f8d93251691aa75a497a702c Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 7 Apr 2017 17:47:53 -0700 Subject: [PATCH 02/76] Check in viveMotionCapture test script. --- .../animation/src/AnimInverseKinematics.cpp | 4 + scripts/developer/tests/viveMotionCapture.js | 198 ++++++++++++++++++ 2 files changed, 202 insertions(+) create mode 100644 scripts/developer/tests/viveMotionCapture.js diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index a27fd01b3c..7fda318f5c 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -277,6 +277,9 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe const float MIN_AXIS_LENGTH = 1.0e-4f; RotationConstraint* constraint = getConstraint(pivotIndex); + + // AJT: disabled special case for the lower spine. + /* if (constraint && constraint->isLowerSpine() && tipIndex != _headIndex) { // for these types of targets we only allow twist at the lower-spine // (this prevents the hand targets from bending the spine too much and thereby driving the hips too far) @@ -292,6 +295,7 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe targetLine = Vectors::ZERO; } } + */ glm::vec3 axis = glm::cross(leverArm, targetLine); float axisLength = glm::length(axis); diff --git a/scripts/developer/tests/viveMotionCapture.js b/scripts/developer/tests/viveMotionCapture.js new file mode 100644 index 0000000000..0e6119a714 --- /dev/null +++ b/scripts/developer/tests/viveMotionCapture.js @@ -0,0 +1,198 @@ +/* global Xform */ +Script.include("/~/system/libraries/Xform.js"); + +var TRACKED_OBJECT_POSES = [ + "TrackedObject00", "TrackedObject01", "TrackedObject02", "TrackedObject03", + "TrackedObject04", "TrackedObject05", "TrackedObject06", "TrackedObject07", + "TrackedObject08", "TrackedObject09", "TrackedObject10", "TrackedObject11", + "TrackedObject12", "TrackedObject13", "TrackedObject14", "TrackedObject15" +]; + +var calibrated = false; +var rightTriggerPressed = false; +var leftTriggerPressed = false; + +var MAPPING_NAME = "com.highfidelity.viveMotionCapture"; + +var mapping = Controller.newMapping(MAPPING_NAME); +mapping.from([Controller.Standard.RTClick]).peek().to(function (value) { + rightTriggerPressed = (value !== 0) ? true : false; +}); +mapping.from([Controller.Standard.LTClick]).peek().to(function (value) { + leftTriggerPressed = (value !== 0) ? true : false; +}); + +Controller.enableMapping(MAPPING_NAME); + +var leftFoot; +var rightFoot; +var hips; + +var Y_180 = {x: 0, y: 1, z: 0, w: 0}; + +function computeOffsetXform(pose, jointIndex) { + var poseXform = new Xform(pose.rotation, pose.translation); + var referenceXform = new Xform(MyAvatar.getAbsoluteDefaultJointRotationInObjectFrame(jointIndex), + MyAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(jointIndex)); + return Xform.mul(poseXform.inv(), referenceXform); +} + +function calibrate() { + print("AJT: calibrating"); + var poses = []; + if (Controller.Hardware.Vive) { + TRACKED_OBJECT_POSES.forEach(function (key) { + var channel = Controller.Hardware.Vive[key]; + var pose = Controller.getPoseValue(channel); + if (pose.valid) { + poses.push({ + channel: channel, + pose: pose + }); + } + }); + } + + if (poses.length >= 2) { + // sort by y + poses.sort(function(a, b) { + var ay = a.pose.translation.y; + var by = b.pose.translation.y; + return ay - by; + }); + + if (poses[0].pose.translation.x > poses[1].pose.translation.x) { + rightFoot = poses[0]; + leftFoot = poses[1]; + } else { + rightFoot = poses[1]; + leftFoot = poses[0]; + } + + // compute offsets + rightFoot.offsetXform = computeOffsetXform(rightFoot.pose, MyAvatar.getJointIndex("RightFoot")); + leftFoot.offsetXform = computeOffsetXform(leftFoot.pose, MyAvatar.getJointIndex("LeftFoot")); + + print("AJT: rightFoot = " + JSON.stringify(rightFoot)); + print("AJT: leftFoot = " + JSON.stringify(leftFoot)); + + if (poses.length >= 3) { + hips = poses[2]; + hips.offsetXform = computeOffsetXform(hips.pose, MyAvatar.getJointIndex("Hips")); + + print("AJT: hips = " + JSON.stringify(hips)); + } + } else { + print("AJT: could not find two trackers, try again!"); + } +} + +var ikTypes = { + RotationAndPosition: 0, + RotationOnly: 1, + HmdHead: 2, + HipsRelativeRotationAndPosition: 3, + Off: 4 +}; + +var handlerId; + +function update(dt) { + if (rightTriggerPressed && leftTriggerPressed) { + if (!calibrated) { + calibrate(); + calibrated = true; + + if (handlerId) { + MyAvatar.removeAnimationStateHandler(handlerId); + } + + // hook up anim state callback + var propList = [ + "leftFootType", "leftFootPosition", "leftFootRotation", + "rightFootType", "rightFootPosition", "rightFootRotation", + "hipsType", "hipsPosition", "hipsRotation" + ]; + + handlerId = MyAvatar.addAnimationStateHandler(function (props) { + + var result = {}, pose, offsetXform, xform; + if (rightFoot) { + pose = Controller.getPoseValue(rightFoot.channel); + offsetXform = rightFoot.offsetXform; + + xform = Xform.mul(new Xform(pose.rotation, pose.translation), offsetXform); + result.rightFootType = ikTypes.RotationAndPosition; + result.rightFootPosition = Vec3.multiplyQbyV(Y_180, xform.pos); + result.rightFootRotation = Quat.multiply(Y_180, xform.rot); + + } else { + result.rightFootType = props.rightFootType; + result.rightFootPositon = props.rightFootPosition; + result.rightFootRotation = props.rightFootRotation; + } + + if (leftFoot) { + pose = Controller.getPoseValue(leftFoot.channel); + offsetXform = leftFoot.offsetXform; + xform = Xform.mul(new Xform(pose.rotation, pose.translation), offsetXform); + result.leftFootType = ikTypes.RotationAndPosition; + result.leftFootPosition = Vec3.multiplyQbyV(Y_180, xform.pos); + result.leftFootRotation = Quat.multiply(Y_180, xform.rot); + } else { + result.leftFootType = props.leftFootType; + result.leftFootPositon = props.leftFootPosition; + result.leftFootRotation = props.leftFootRotation; + } + + if (hips) { + pose = Controller.getPoseValue(hips.channel); + offsetXform = hips.offsetXform; + xform = Xform.mul(new Xform(pose.rotation, pose.translation), offsetXform); + result.hipsType = ikTypes.RotationAndPosition; + result.hipsPosition = Vec3.multiplyQbyV(Y_180, xform.pos); + result.hipsRotation = Quat.multiply(Y_180, xform.rot); + } else { + result.hipsType = props.hipsType; + result.hipsPositon = props.hipsPosition; + result.hipsRotation = props.hipsRotation; + } + + return result; + }, propList); + + } + } else { + calibrated = false; + } + + var drawMarkers = false; + if (drawMarkers) { + var RED = {x: 1, y: 0, z: 0, w: 1}; + var GREEN = {x: 0, y: 1, z: 0, w: 1}; + var BLUE = {x: 0, y: 0, z: 1, w: 1}; + + if (leftFoot) { + var leftFootPose = Controller.getPoseValue(leftFoot.channel); + DebugDraw.addMyAvatarMarker("leftFootTracker", leftFootPose.rotation, leftFootPose.translation, BLUE); + } + + if (rightFoot) { + var rightFootPose = Controller.getPoseValue(rightFoot.channel); + DebugDraw.addMyAvatarMarker("rightFootTracker", rightFootPose.rotation, rightFootPose.translation, RED); + } + + if (hips) { + var hipsPose = Controller.getPoseValue(hips.channel); + DebugDraw.addMyAvatarMarker("hipsTracker", hipsPose.rotation, hipsPose.translation, GREEN); + } + } +} + +Script.update.connect(update); + +Script.scriptEnding.connect(function () { + Controller.disableMapping(MAPPING_NAME); + Script.update.disconnect(update); +}); +var TRIGGER_OFF_VALUE = 0.1; From 0ebaba7cf890aa7544365cff5057c87a87642e33 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 10 Apr 2017 13:36:30 -0700 Subject: [PATCH 03/76] Now supports sensorConfig with hips and chest sensors --- .../resources/avatar/avatar-animation.json | 8 +- libraries/animation/src/Rig.cpp | 3 + scripts/developer/tests/viveMotionCapture.js | 123 +++++++++++++----- scripts/developer/tests/viveTrackedObjects.js | 4 +- 4 files changed, 100 insertions(+), 38 deletions(-) diff --git a/interface/resources/avatar/avatar-animation.json b/interface/resources/avatar/avatar-animation.json index daa69b95f7..9efe3dd29b 100644 --- a/interface/resources/avatar/avatar-animation.json +++ b/interface/resources/avatar/avatar-animation.json @@ -81,10 +81,10 @@ "typeVar": "leftFootType" }, { - "jointName": "Neck", - "positionVar": "neckPosition", - "rotationVar": "neckRotation", - "typeVar": "neckType" + "jointName": "Spine2", + "positionVar": "spine2Position", + "rotationVar": "spine2Rotation", + "typeVar": "spine2Type" }, { "jointName": "Head", diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index adc40d6e81..c8717fd11e 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1033,6 +1033,9 @@ void Rig::updateFromHeadParameters(const HeadParameters& params, float dt) { } else { _animVars.set("hipsType", (int)IKTarget::Type::Unknown); } + + // by default this IK target is disabled. + _animVars.set("spine2Type", (int)IKTarget::Type::Unknown); } void Rig::updateFromEyeParameters(const EyeParameters& params) { diff --git a/scripts/developer/tests/viveMotionCapture.js b/scripts/developer/tests/viveMotionCapture.js index 0e6119a714..27c6809f5c 100644 --- a/scripts/developer/tests/viveMotionCapture.js +++ b/scripts/developer/tests/viveMotionCapture.js @@ -27,18 +27,59 @@ Controller.enableMapping(MAPPING_NAME); var leftFoot; var rightFoot; var hips; +var spine2; + +var FEET_ONLY = 0; +var FEET_AND_HIPS = 1; +var FEET_AND_CHEST = 2; +var FEET_HIPS_AND_CHEST = 3; + +var SENSOR_CONFIG_NAMES = [ + "FeetOnly", + "FeetAndHips", + "FeetAndChest", + "FeetHipsAndChest" +]; + +var ANIM_VARS = [ + "leftFootType", + "leftFootPosition", + "leftFootRotation", + "rightFootType", + "rightFootPosition", + "rightFootRotation", + "hipsType", + "hipsPosition", + "hipsRotation", + "spine2Type", + "spine2Position", + "spine2Rotation" +]; + +var sensorConfig = FEET_HIPS_AND_CHEST; var Y_180 = {x: 0, y: 1, z: 0, w: 0}; +var Y_180_XFORM = new Xform(Y_180, {x: 0, y: 0, z: 0}); function computeOffsetXform(pose, jointIndex) { var poseXform = new Xform(pose.rotation, pose.translation); + + // TODO: we can do better here... + // one idea, hang default pose skeleton from HMD head. var referenceXform = new Xform(MyAvatar.getAbsoluteDefaultJointRotationInObjectFrame(jointIndex), MyAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(jointIndex)); + return Xform.mul(poseXform.inv(), referenceXform); } function calibrate() { - print("AJT: calibrating"); + print("AJT: calibrating..."); + + leftFoot = undefined; + rightFoot = undefined; + hips = undefined; + spine2 = undefined; + var poses = []; if (Controller.Hardware.Vive) { TRACKED_OBJECT_POSES.forEach(function (key) { @@ -76,12 +117,30 @@ function calibrate() { print("AJT: rightFoot = " + JSON.stringify(rightFoot)); print("AJT: leftFoot = " + JSON.stringify(leftFoot)); - if (poses.length >= 3) { + if (sensorConfig === FEET_ONLY) { + // we're done! + } else if (sensorConfig === FEET_AND_HIPS && poses.length >= 3) { hips = poses[2]; - hips.offsetXform = computeOffsetXform(hips.pose, MyAvatar.getJointIndex("Hips")); + } else if (sensorConfig === FEET_AND_CHEST && poses.length >= 3) { + spine2 = poses[2]; + } else if (sensorConfig === FEET_HIPS_AND_CHEST && poses.length >= 4) { + hips = poses[2]; + spine2 = poses[3]; + } else { + // TODO: better error messages + print("AJT: could not calibrate for sensor config " + SENSOR_CONFIG_NAMES[sensorConfig] + ", please try again!"); + } + if (hips) { + hips.offsetXform = computeOffsetXform(hips.pose, MyAvatar.getJointIndex("Hips")); print("AJT: hips = " + JSON.stringify(hips)); } + + if (spine2) { + spine2.offsetXform = computeOffsetXform(spine2.pose, MyAvatar.getJointIndex("Spine2")); + print("AJT: spine2 = " + JSON.stringify(spine2)); + } + } else { print("AJT: could not find two trackers, try again!"); } @@ -97,6 +156,12 @@ var ikTypes = { var handlerId; +function computeIKTargetXform(jointInfo) { + var pose = Controller.getPoseValue(jointInfo.channel); + var offsetXform = jointInfo.offsetXform; + return Xform.mul(Y_180_XFORM, Xform.mul(new Xform(pose.rotation, pose.translation), offsetXform)); +} + function update(dt) { if (rightTriggerPressed && leftTriggerPressed) { if (!calibrated) { @@ -107,59 +172,53 @@ function update(dt) { MyAvatar.removeAnimationStateHandler(handlerId); } - // hook up anim state callback - var propList = [ - "leftFootType", "leftFootPosition", "leftFootRotation", - "rightFootType", "rightFootPosition", "rightFootRotation", - "hipsType", "hipsPosition", "hipsRotation" - ]; - handlerId = MyAvatar.addAnimationStateHandler(function (props) { - var result = {}, pose, offsetXform, xform; + var result = {}, xform; if (rightFoot) { - pose = Controller.getPoseValue(rightFoot.channel); - offsetXform = rightFoot.offsetXform; - - xform = Xform.mul(new Xform(pose.rotation, pose.translation), offsetXform); + xform = computeIKTargetXform(rightFoot); result.rightFootType = ikTypes.RotationAndPosition; - result.rightFootPosition = Vec3.multiplyQbyV(Y_180, xform.pos); - result.rightFootRotation = Quat.multiply(Y_180, xform.rot); - + result.rightFootPosition = xform.pos; + result.rightFootRotation = xform.rot; } else { result.rightFootType = props.rightFootType; - result.rightFootPositon = props.rightFootPosition; + result.rightFootPosition = props.rightFootPosition; result.rightFootRotation = props.rightFootRotation; } if (leftFoot) { - pose = Controller.getPoseValue(leftFoot.channel); - offsetXform = leftFoot.offsetXform; - xform = Xform.mul(new Xform(pose.rotation, pose.translation), offsetXform); + xform = computeIKTargetXform(leftFoot); result.leftFootType = ikTypes.RotationAndPosition; - result.leftFootPosition = Vec3.multiplyQbyV(Y_180, xform.pos); - result.leftFootRotation = Quat.multiply(Y_180, xform.rot); + result.leftFootPosition = xform.pos; + result.leftFootRotation = xform.rot; } else { result.leftFootType = props.leftFootType; - result.leftFootPositon = props.leftFootPosition; + result.leftFootPosition = props.leftFootPosition; result.leftFootRotation = props.leftFootRotation; } if (hips) { - pose = Controller.getPoseValue(hips.channel); - offsetXform = hips.offsetXform; - xform = Xform.mul(new Xform(pose.rotation, pose.translation), offsetXform); + xform = computeIKTargetXform(hips); result.hipsType = ikTypes.RotationAndPosition; - result.hipsPosition = Vec3.multiplyQbyV(Y_180, xform.pos); - result.hipsRotation = Quat.multiply(Y_180, xform.rot); + result.hipsPosition = xform.pos; + result.hipsRotation = xform.rot; } else { result.hipsType = props.hipsType; - result.hipsPositon = props.hipsPosition; + result.hipsPosition = props.hipsPosition; result.hipsRotation = props.hipsRotation; } + if (spine2) { + xform = computeIKTargetXform(spine2); + result.spine2Type = ikTypes.RotationAndPosition; + result.spine2Position = xform.pos; + result.spine2Rotation = xform.rot; + } else { + result.spine2Type = ikTypes.Off; + } + return result; - }, propList); + }, ANIM_VARS); } } else { diff --git a/scripts/developer/tests/viveTrackedObjects.js b/scripts/developer/tests/viveTrackedObjects.js index 78911538e4..4155afb82b 100644 --- a/scripts/developer/tests/viveTrackedObjects.js +++ b/scripts/developer/tests/viveTrackedObjects.js @@ -18,14 +18,14 @@ function shutdown() { }); } -var WHITE = {x: 1, y: 1, z: 1, w: 1}; +var BLUE = {x: 0, y: 0, z: 1, w: 1}; function update(dt) { if (Controller.Hardware.Vive) { TRACKED_OBJECT_POSES.forEach(function (key) { var pose = Controller.getPoseValue(Controller.Hardware.Vive[key]); if (pose.valid) { - DebugDraw.addMyAvatarMarker(key, pose.rotation, pose.translation, WHITE); + DebugDraw.addMyAvatarMarker(key, pose.rotation, pose.translation, BLUE); } else { DebugDraw.removeMyAvatarMarker(key); } From d3b4a2c08d968da1ce0b2ef6a54926e350b10b5f Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 10 Apr 2017 15:08:03 -0700 Subject: [PATCH 04/76] better "auto" configuration --- libraries/script-engine/src/Quat.cpp | 7 ++ libraries/script-engine/src/Quat.h | 2 + scripts/developer/tests/viveMotionCapture.js | 86 ++++++++++++++++---- 3 files changed, 77 insertions(+), 18 deletions(-) diff --git a/libraries/script-engine/src/Quat.cpp b/libraries/script-engine/src/Quat.cpp index 6d49ed27c1..05002dcf5d 100644 --- a/libraries/script-engine/src/Quat.cpp +++ b/libraries/script-engine/src/Quat.cpp @@ -122,3 +122,10 @@ bool Quat::equal(const glm::quat& q1, const glm::quat& q2) { return q1 == q2; } +glm::quat Quat::cancelOutRollAndPitch(const glm::quat& q) { + return ::cancelOutRollAndPitch(q); +} + +glm::quat Quat::cancelOutRoll(const glm::quat& q) { + return ::cancelOutRoll(q); +} diff --git a/libraries/script-engine/src/Quat.h b/libraries/script-engine/src/Quat.h index 8a88767a41..ee3ab9aa7c 100644 --- a/libraries/script-engine/src/Quat.h +++ b/libraries/script-engine/src/Quat.h @@ -60,6 +60,8 @@ public slots: float dot(const glm::quat& q1, const glm::quat& q2); void print(const QString& label, const glm::quat& q); bool equal(const glm::quat& q1, const glm::quat& q2); + glm::quat cancelOutRollAndPitch(const glm::quat& q); + glm::quat cancelOutRoll(const glm::quat& q); }; #endif // hifi_Quat_h diff --git a/scripts/developer/tests/viveMotionCapture.js b/scripts/developer/tests/viveMotionCapture.js index 27c6809f5c..6cb0f92b9b 100644 --- a/scripts/developer/tests/viveMotionCapture.js +++ b/scripts/developer/tests/viveMotionCapture.js @@ -33,12 +33,14 @@ var FEET_ONLY = 0; var FEET_AND_HIPS = 1; var FEET_AND_CHEST = 2; var FEET_HIPS_AND_CHEST = 3; +var AUTO = 4; var SENSOR_CONFIG_NAMES = [ "FeetOnly", "FeetAndHips", "FeetAndChest", - "FeetHipsAndChest" + "FeetHipsAndChest", + "Auto" ]; var ANIM_VARS = [ @@ -56,30 +58,47 @@ var ANIM_VARS = [ "spine2Rotation" ]; -var sensorConfig = FEET_HIPS_AND_CHEST; +var sensorConfig = AUTO; var Y_180 = {x: 0, y: 1, z: 0, w: 0}; var Y_180_XFORM = new Xform(Y_180, {x: 0, y: 0, z: 0}); -function computeOffsetXform(pose, jointIndex) { +function computeOffsetXform(defaultToReferenceXform, pose, jointIndex) { var poseXform = new Xform(pose.rotation, pose.translation); - // TODO: we can do better here... - // one idea, hang default pose skeleton from HMD head. - var referenceXform = new Xform(MyAvatar.getAbsoluteDefaultJointRotationInObjectFrame(jointIndex), - MyAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(jointIndex)); + var defaultJointXform = new Xform(MyAvatar.getAbsoluteDefaultJointRotationInObjectFrame(jointIndex), + MyAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(jointIndex)); - return Xform.mul(poseXform.inv(), referenceXform); + var referenceJointXform = Xform.mul(defaultToReferenceXform, defaultJointXform); + + return Xform.mul(poseXform.inv(), referenceJointXform); +} + +function computeDefaultToReferenceXform() { + var headIndex = MyAvatar.getJointIndex("Head"); + if (headIndex >= 0) { + var defaultHeadXform = new Xform(MyAvatar.getAbsoluteDefaultJointRotationInObjectFrame(headIndex), + MyAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(headIndex)); + var currentHeadXform = new Xform(Quat.cancelOutRollAndPitch(MyAvatar.getAbsoluteJointRotationInObjectFrame(headIndex)), + MyAvatar.getAbsoluteJointTranslationInObjectFrame(headIndex)); + + var defaultToReferenceXform = Xform.mul(currentHeadXform, defaultHeadXform.inv()); + + return defaultToReferenceXform; + } else { + return new Xform.ident(); + } } function calibrate() { - print("AJT: calibrating..."); leftFoot = undefined; rightFoot = undefined; hips = undefined; spine2 = undefined; + var defaultToReferenceXform = computeDefaultToReferenceXform(); + var poses = []; if (Controller.Hardware.Vive) { TRACKED_OBJECT_POSES.forEach(function (key) { @@ -94,6 +113,23 @@ function calibrate() { }); } + print("AJT: calibrating, num tracked poses = " + poses.length + ", sensorConfig = " + SENSOR_CONFIG_NAMES[sensorConfig]); + + var config = sensorConfig; + + if (config === AUTO) { + if (poses.length === 2) { + config = FEET_ONLY; + } else if (poses.length === 3) { + config = FEET_AND_HIPS; + } else if (poses.length >= 4) { + config = FEET_HIPS_AND_CHEST; + } else { + print("AJT: auto config failed: poses.length = " + poses.length); + config = FEET_ONLY; + } + } + if (poses.length >= 2) { // sort by y poses.sort(function(a, b) { @@ -111,33 +147,33 @@ function calibrate() { } // compute offsets - rightFoot.offsetXform = computeOffsetXform(rightFoot.pose, MyAvatar.getJointIndex("RightFoot")); - leftFoot.offsetXform = computeOffsetXform(leftFoot.pose, MyAvatar.getJointIndex("LeftFoot")); + rightFoot.offsetXform = computeOffsetXform(defaultToReferenceXform, rightFoot.pose, MyAvatar.getJointIndex("RightFoot")); + leftFoot.offsetXform = computeOffsetXform(defaultToReferenceXform, leftFoot.pose, MyAvatar.getJointIndex("LeftFoot")); print("AJT: rightFoot = " + JSON.stringify(rightFoot)); print("AJT: leftFoot = " + JSON.stringify(leftFoot)); - if (sensorConfig === FEET_ONLY) { + if (config === FEET_ONLY) { // we're done! - } else if (sensorConfig === FEET_AND_HIPS && poses.length >= 3) { + } else if (config === FEET_AND_HIPS && poses.length >= 3) { hips = poses[2]; - } else if (sensorConfig === FEET_AND_CHEST && poses.length >= 3) { + } else if (config === FEET_AND_CHEST && poses.length >= 3) { spine2 = poses[2]; - } else if (sensorConfig === FEET_HIPS_AND_CHEST && poses.length >= 4) { + } else if (config === FEET_HIPS_AND_CHEST && poses.length >= 4) { hips = poses[2]; spine2 = poses[3]; } else { // TODO: better error messages - print("AJT: could not calibrate for sensor config " + SENSOR_CONFIG_NAMES[sensorConfig] + ", please try again!"); + print("AJT: could not calibrate for sensor config " + SENSOR_CONFIG_NAMES[config] + ", please try again!"); } if (hips) { - hips.offsetXform = computeOffsetXform(hips.pose, MyAvatar.getJointIndex("Hips")); + hips.offsetXform = computeOffsetXform(defaultToReferenceXform, hips.pose, MyAvatar.getJointIndex("Hips")); print("AJT: hips = " + JSON.stringify(hips)); } if (spine2) { - spine2.offsetXform = computeOffsetXform(spine2.pose, MyAvatar.getJointIndex("Spine2")); + spine2.offsetXform = computeOffsetXform(defaultToReferenceXform, spine2.pose, MyAvatar.getJointIndex("Spine2")); print("AJT: spine2 = " + JSON.stringify(spine2)); } @@ -246,6 +282,20 @@ function update(dt) { DebugDraw.addMyAvatarMarker("hipsTracker", hipsPose.rotation, hipsPose.translation, GREEN); } } + + var drawReferencePose = false; + if (drawReferencePose) { + var GREEN = {x: 0, y: 1, z: 0, w: 1}; + var defaultToReferenceXform = computeDefaultToReferenceXform(); + var leftFootIndex = MyAvatar.getJointIndex("LeftFoot"); + if (leftFootIndex > 0) { + var defaultLeftFootXform = new Xform(MyAvatar.getAbsoluteDefaultJointRotationInObjectFrame(leftFootIndex), + MyAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(leftFootIndex)); + var referenceLeftFootXform = Xform.mul(defaultToReferenceXform, defaultLeftFootXform); + DebugDraw.addMyAvatarMarker("leftFootTracker", referenceLeftFootXform.rot, referenceLeftFootXform.pos, GREEN); + } + } + } Script.update.connect(update); From d464020577bbb3336e9153f9a21b55af65af03b8 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 10 Apr 2017 16:03:34 -0700 Subject: [PATCH 05/76] Adjust min angle of knee constraint to prevent leg locks --- libraries/animation/src/AnimInverseKinematics.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 7fda318f5c..d6c976ce21 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -829,7 +829,7 @@ void AnimInverseKinematics::initConstraints() { // we determine the max/min angles by rotating the swing limit lines from parent- to child-frame // then measure the angles to swing the yAxis into alignment - const float MIN_KNEE_ANGLE = 0.0f; + const float MIN_KNEE_ANGLE = 0.097f; // ~5 deg const float MAX_KNEE_ANGLE = 7.0f * PI / 8.0f; glm::quat invReferenceRotation = glm::inverse(referenceRotation); glm::vec3 minSwingAxis = invReferenceRotation * glm::angleAxis(MIN_KNEE_ANGLE, hingeAxis) * Vectors::UNIT_Y; From b49760cee2a729cc177c7c3cc7a126489868ab98 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 10 Apr 2017 16:52:46 -0700 Subject: [PATCH 06/76] Remove hand IK target collision with body capsule --- libraries/animation/src/Rig.cpp | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index c8717fd11e..dd7ccb6b27 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1174,15 +1174,7 @@ void Rig::updateFromHandAndFeetParameters(const HandAndFeetParameters& params, f const glm::vec3 bodyCapsuleEnd = bodyCapsuleCenter + glm::vec3(0, params.bodyCapsuleHalfHeight, 0); if (params.isLeftEnabled) { - - // prevent the hand IK targets from intersecting the body capsule - glm::vec3 handPosition = params.leftPosition; - glm::vec3 displacement; - if (findSphereCapsulePenetration(handPosition, HAND_RADIUS, bodyCapsuleStart, bodyCapsuleEnd, bodyCapsuleRadius, displacement)) { - handPosition -= displacement; - } - - _animVars.set("leftHandPosition", handPosition); + _animVars.set("leftHandPosition", params.leftPosition); _animVars.set("leftHandRotation", params.leftOrientation); _animVars.set("leftHandType", (int)IKTarget::Type::RotationAndPosition); } else { @@ -1192,15 +1184,7 @@ void Rig::updateFromHandAndFeetParameters(const HandAndFeetParameters& params, f } if (params.isRightEnabled) { - - // prevent the hand IK targets from intersecting the body capsule - glm::vec3 handPosition = params.rightPosition; - glm::vec3 displacement; - if (findSphereCapsulePenetration(handPosition, HAND_RADIUS, bodyCapsuleStart, bodyCapsuleEnd, bodyCapsuleRadius, displacement)) { - handPosition -= displacement; - } - - _animVars.set("rightHandPosition", handPosition); + _animVars.set("rightHandPosition", params.rightPosition); _animVars.set("rightHandRotation", params.rightOrientation); _animVars.set("rightHandType", (int)IKTarget::Type::RotationAndPosition); } else { From 22e79504bb71a380e8f4692550e8aa25340e16ec Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 14 Apr 2017 16:57:30 -0700 Subject: [PATCH 07/76] Elliptical swing targets for the spine, Bug fix for debug draw --- .../animation/src/AnimInverseKinematics.cpp | 51 ++++++++++++------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index d6c976ce21..0e7a41e74c 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -149,7 +149,7 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const std::vector MAX_ERROR_TOLERANCE && numLoops < MAX_IK_LOOPS) { ++numLoops; @@ -365,7 +365,9 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe } // store the relative rotation change in the accumulator - _accumulators[pivotIndex].add(newRot, target.getWeight()); + // AJT: Hack give head more weight. + float weight = (target.getIndex() == _headIndex) ? 2.0f : 1.0f; + _accumulators[pivotIndex].add(newRot, weight); // this joint has been changed so we check to see if it has the lowest index if (pivotIndex < lowestMovedIndex) { @@ -445,8 +447,6 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars computeTargets(animVars, targets, underPoses); } - _previousEnableDebugIKTargets = context.getEnableDebugDrawIKTargets(); - if (targets.empty()) { // no IK targets but still need to enforce constraints std::map::iterator constraintItr = _constraints.begin(); @@ -510,6 +510,8 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars DebugDraw::getInstance().removeMyAvatarMarker(name); } } + + _previousEnableDebugIKTargets = context.getEnableDebugDrawIKTargets(); } { @@ -551,6 +553,22 @@ void AnimInverseKinematics::clearConstraints() { _constraints.clear(); } +// set up swing limits around a swingTwistConstraint in an ellipse, where lateralSwingTheta is the swing limit for lateral swings (side to side) +// anteriorSwingTheta is swing limit for forward and backward swings. (where x-axis of reference rotation is sideways and -z-axis is forward) +static void setEllipticalSwingLimits(SwingTwistConstraint* stConstraint, float lateralSwingTheta, float anteriorSwingTheta) { + assert(stConstraint); + const int NUM_SUBDIVISIONS = 8; + std::vector minDots; + minDots.reserve(NUM_SUBDIVISIONS); + float dTheta = (2.0f * PI) / NUM_SUBDIVISIONS; + float theta = 0.0f; + for (int i = 0; i < NUM_SUBDIVISIONS; i++) { + minDots.push_back(cosf(glm::length(glm::vec2(anteriorSwingTheta * cosf(theta), lateralSwingTheta * sinf(theta))))); + theta += dTheta; + } + stConstraint->setSwingLimits(minDots); +} + void AnimInverseKinematics::initConstraints() { if (!_skeleton) { return; @@ -740,41 +758,40 @@ void AnimInverseKinematics::initConstraints() { } else if (baseName.startsWith("Spine", Qt::CaseSensitive)) { SwingTwistConstraint* stConstraint = new SwingTwistConstraint(); stConstraint->setReferenceRotation(_defaultRelativePoses[i].rot()); - const float MAX_SPINE_TWIST = PI / 12.0f; + const float MAX_SPINE_TWIST = PI / 20.0f; stConstraint->setTwistLimits(-MAX_SPINE_TWIST, MAX_SPINE_TWIST); + /* std::vector minDots; const float MAX_SPINE_SWING = PI / 10.0f; minDots.push_back(cosf(MAX_SPINE_SWING)); stConstraint->setSwingLimits(minDots); + */ + + // AJT: limit lateral swings more then forward-backward swings + setEllipticalSwingLimits(stConstraint, PI / 30.0f, PI / 20.0f); + if (0 == baseName.compare("Spine1", Qt::CaseSensitive) || 0 == baseName.compare("Spine", Qt::CaseSensitive)) { stConstraint->setLowerSpine(true); } constraint = static_cast(stConstraint); - } else if (baseName.startsWith("Hips2", Qt::CaseSensitive)) { - SwingTwistConstraint* stConstraint = new SwingTwistConstraint(); - stConstraint->setReferenceRotation(_defaultRelativePoses[i].rot()); - const float MAX_SPINE_TWIST = PI / 8.0f; - stConstraint->setTwistLimits(-MAX_SPINE_TWIST, MAX_SPINE_TWIST); - std::vector minDots; - const float MAX_SPINE_SWING = PI / 14.0f; - minDots.push_back(cosf(MAX_SPINE_SWING)); - stConstraint->setSwingLimits(minDots); - - constraint = static_cast(stConstraint); } else if (0 == baseName.compare("Neck", Qt::CaseSensitive)) { SwingTwistConstraint* stConstraint = new SwingTwistConstraint(); stConstraint->setReferenceRotation(_defaultRelativePoses[i].rot()); - const float MAX_NECK_TWIST = PI / 9.0f; + const float MAX_NECK_TWIST = PI / 10.0f; stConstraint->setTwistLimits(-MAX_NECK_TWIST, MAX_NECK_TWIST); + /* std::vector minDots; const float MAX_NECK_SWING = PI / 8.0f; minDots.push_back(cosf(MAX_NECK_SWING)); stConstraint->setSwingLimits(minDots); + */ + + setEllipticalSwingLimits(stConstraint, PI / 10.0f, PI / 8.0f); constraint = static_cast(stConstraint); } else if (0 == baseName.compare("Head", Qt::CaseSensitive)) { From c5f1455872b5b7b20e61db98a0b3aef97af29f38 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 19 Apr 2017 12:47:28 -0700 Subject: [PATCH 08/76] go horizontal --- interface/resources/qml/hifi/Feed.qml | 78 +++++++++++++++++++ .../qml/hifi/tablet/TabletAddressDialog.qml | 72 +++++------------ 2 files changed, 96 insertions(+), 54 deletions(-) create mode 100644 interface/resources/qml/hifi/Feed.qml diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml new file mode 100644 index 0000000000..1adeec8bf2 --- /dev/null +++ b/interface/resources/qml/hifi/Feed.qml @@ -0,0 +1,78 @@ +// +// Feed.qml +// qml/hifi +// +// Displays a particular type of feed +// +// Created by Howard Stearns on 4/18/2017 +// 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 Hifi 1.0 +import QtQuick 2.5 +import QtGraphicalEffects 1.0 +import "toolbars" +import "../styles-uit" + +Rectangle { + id: root; + + property int cardWidth: 212; + property int cardHeight: 152; + property int stackedCardShadowHeight: 10; + property alias suggestions: feed; + + HifiConstants { id: hifi } + ListModel { id: feed; } + + RalewayLight { + id: label; + text: "Places"; + color: hifi.colors.blueHighlight; + size: 38; + width: root.width; + anchors { + top: parent.top; + left: parent.left; + margins: 10; + } + } + + ListView { + id: scroll; + clip: true; + model: feed; + orientation: ListView.Horizontal; + + spacing: 14; + height: cardHeight + stackedCardShadowHeight; + anchors { + top: label.bottom; + left: parent.left; + right: parent.right; + leftMargin: 10; + } + + delegate: Card { + width: cardWidth; + height: cardHeight; + goFunction: goCard; + userName: model.username; + placeName: model.place_name; + hifiUrl: model.place_name + model.path; + thumbnail: model.thumbnail_url; + imageUrl: model.image_url; + action: model.action; + timestamp: model.created_at; + onlineUsers: model.online_users; + storyId: model.metaverseId; + drillDownToPlace: model.drillDownToPlace; + shadowHeight: stackedCardShadowHeight; + hoverThunk: function () { scroll.currentIndex = index; } + unhoverThunk: function () { scroll.currentIndex = -1; } + } + } +} diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index bed1f82ac2..b97eaeafdb 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -31,8 +31,8 @@ StackView { height: parent !== null ? parent.height : undefined property var eventBridge; property var allStories: []; - property int cardWidth: 460; - property int cardHeight: 320; + property int cardWidth: 212; + property int cardHeight: 152; property string metaverseBase: addressBarDialog.metaverseServerUrl + "/api/v1/"; property var tablet: null; @@ -275,58 +275,21 @@ StackView { } Rectangle { - id: bgMain - color: hifiStyleConstants.colors.white - anchors.bottom: parent.keyboardEnabled ? keyboard.top : parent.bottom - anchors.bottomMargin: 0 - anchors.right: parent.right - anchors.rightMargin: 0 - anchors.left: parent.left - anchors.leftMargin: 0 - anchors.top: topBar.bottom - anchors.topMargin: 0 - - ListModel { id: suggestions } - - ListView { - id: scroll - - property int stackedCardShadowHeight: 0; - clip: true - spacing: 14 - anchors { - bottom: parent.bottom - top: parent.top - left: parent.left - right: parent.right - leftMargin: 10 - } - - model: suggestions - orientation: ListView.Vertical - - delegate: Card { - width: cardWidth; - height: cardHeight; - goFunction: goCard; - userName: model.username; - placeName: model.place_name; - hifiUrl: model.place_name + model.path; - thumbnail: model.thumbnail_url; - imageUrl: model.image_url; - action: model.action; - timestamp: model.created_at; - onlineUsers: model.online_users; - storyId: model.metaverseId; - drillDownToPlace: model.drillDownToPlace; - shadowHeight: scroll.stackedCardShadowHeight; - hoverThunk: function () { scroll.currentIndex = index; } - unhoverThunk: function () { scroll.currentIndex = -1; } - } - - highlightMoveDuration: -1; - highlightMoveVelocity: -1; - highlight: Rectangle { color: "transparent"; border.width: 4; border.color: hifiStyleConstants.colors.blueHighlight; z: 1; } + id: bgMain; + color: hifiStyleConstants.colors.white; + anchors { + bottom: parent.keyboardEnabled ? keyboard.top : parent.bottom; + bottomMargin: 0; + right: parent.right; + rightMargin: 0; + left: parent.left; + leftMargin: 0; + top: topBar.bottom; + topMargin: 0; + } + Feed { + id: feed; + width: bgMain.width; } } @@ -363,6 +326,7 @@ StackView { } } + property alias suggestions: feed.suggestions; function getRequest(url, cb) { // cb(error, responseOfCorrectContentType) of url. General for 'get' text/html/json, but without redirects. // TODO: make available to other .qml. From 3ce238b8a0a8a21ac93740fc4d652ec9575079d7 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 19 Apr 2017 13:45:58 -0700 Subject: [PATCH 09/76] make feed self contained --- interface/resources/qml/hifi/Feed.qml | 122 ++++++++++++++++- .../qml/hifi/tablet/TabletAddressDialog.qml | 123 +----------------- 2 files changed, 122 insertions(+), 123 deletions(-) diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index 1adeec8bf2..2a7156b31e 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -23,11 +23,125 @@ Rectangle { property int cardWidth: 212; property int cardHeight: 152; property int stackedCardShadowHeight: 10; - property alias suggestions: feed; + property string metaverseServerUrl: ''; + property string filter: ''; + onFilterChanged: filterChoicesByText(); + property alias suggestions: feed; // fixme. don't need to expose HifiConstants { id: hifi } ListModel { id: feed; } - + + function resolveUrl(url) { + return (url.indexOf('/') === 0) ? (metaverseServerUrl + url) : url; + } + function makeModelData(data) { // create a new obj from data + // ListModel elements will only ever have those properties that are defined by the first obj that is added. + // So here we make sure that we have all the properties we need, regardless of whether it is a place data or user story. + var name = data.place_name, + tags = data.tags || [data.action, data.username], + description = data.description || "", + thumbnail_url = data.thumbnail_url || ""; + return { + place_name: name, + username: data.username || "", + path: data.path || "", + created_at: data.created_at || "", + action: data.action || "", + thumbnail_url: resolveUrl(thumbnail_url), + image_url: resolveUrl(data.details.image_url), + + metaverseId: (data.id || "").toString(), // Some are strings from server while others are numbers. Model objects require uniformity. + + tags: tags, + description: description, + online_users: data.details.concurrency || 0, + drillDownToPlace: false, + + searchText: [name].concat(tags, description || []).join(' ').toUpperCase() + } + } + property var allStories: []; + property var placeMap: ({}); // Used for making stacks. FIXME: generalize to not just by place. + property int requestId: 0; + function getUserStoryPage(pageNumber, cb) { // cb(error) after all pages of domain data have been added to model + var options = [ + 'now=' + new Date().toISOString(), + 'include_actions=' + selectedTab.includeActions, + 'restriction=' + (Account.isLoggedIn() ? 'open,hifi' : 'open'), + 'require_online=true', + 'protocol=' + encodeURIComponent(AddressManager.protocolVersion()), + 'page=' + pageNumber + ]; + var url = metaverseBase + 'user_stories?' + options.join('&'); + var thisRequestId = ++requestId; + getRequest(url, function (error, data) { + if ((thisRequestId !== requestId) || handleError(url, error, data, cb)) { + return; // abandon stale requests + } + var stories = data.user_stories.map(function (story) { // explicit single-argument function + return makeModelData(story, url); + }); + allStories = allStories.concat(stories); + stories.forEach(makeFilteredPlaceProcessor()); + if ((data.current_page < data.total_pages) && (data.current_page <= 10)) { // just 10 pages = 100 stories for now + return getUserStoryPage(pageNumber + 1, cb); + } + cb(); + }); + } + function fillDestinations() { // Public + allStories = []; + suggestions.clear(); + placeMap = {}; + getUserStoryPage(1, function (error) { + console.log('user stories query', error || 'ok', allStories.length); + }); + } + function addToSuggestions(place) { // fixme: move to makeFilteredPlaceProcessor + var collapse = allTab.selected && (place.action !== 'concurrency'); + if (collapse) { + var existing = placeMap[place.place_name]; + if (existing) { + existing.drillDownToPlace = true; + return; + } + } + suggestions.append(place); + if (collapse) { + placeMap[place.place_name] = suggestions.get(suggestions.count - 1); + } else if (place.action === 'concurrency') { + suggestions.get(suggestions.count - 1).drillDownToPlace = true; // Don't change raw place object (in allStories). + } + } + function suggestable(place) { // fixme add to makeFilteredPlaceProcessor + if (place.action === 'snapshot') { + return true; + } + return (place.place_name !== AddressManager.placename); // Not our entry, but do show other entry points to current domain. + } + function makeFilteredPlaceProcessor() { // answer a function(placeData) that adds it to suggestions if it matches + var words = filter.toUpperCase().split(/\s+/).filter(identity), + data = allStories; + function matches(place) { + if (!words.length) { + return suggestable(place); + } + return words.every(function (word) { + return place.searchText.indexOf(word) >= 0; + }); + } + return function (place) { + if (matches(place)) { + addToSuggestions(place); + } + }; + } + function filterChoicesByText() { + suggestions.clear(); + placeMap = {}; + allStories.forEach(makeFilteredPlaceProcessor()); + } + RalewayLight { id: label; text: "Places"; @@ -40,7 +154,6 @@ Rectangle { margins: 10; } } - ListView { id: scroll; clip: true; @@ -55,11 +168,10 @@ Rectangle { right: parent.right; leftMargin: 10; } - delegate: Card { width: cardWidth; height: cardHeight; - goFunction: goCard; + goFunction: goCard; // fixme global userName: model.username; placeName: model.place_name; hifiUrl: model.place_name + model.path; diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index b97eaeafdb..57e24c2421 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -30,7 +30,6 @@ StackView { width: parent !== null ? parent.width : undefined height: parent !== null ? parent.height : undefined property var eventBridge; - property var allStories: []; property int cardWidth: 212; property int cardHeight: 152; property string metaverseBase: addressBarDialog.metaverseServerUrl + "/api/v1/"; @@ -39,9 +38,8 @@ StackView { Component { id: tabletWebView; TabletWebView {} } Component.onCompleted: { - fillDestinations(); updateLocationText(false); - fillDestinations(); + feed.fillDestinations(); addressLine.focus = !HMD.active; root.parentChanged.connect(center); center(); @@ -189,7 +187,7 @@ StackView { } font.pixelSize: hifi.fonts.pixelSize * 0.75 onTextChanged: { - filterChoicesByText(); + console.log('fixme onTextChanged "' + addressLine.text + "'."); updateLocationText(text.length > 0); } onAccepted: { @@ -290,6 +288,8 @@ StackView { Feed { id: feed; width: bgMain.width; + metaverseServerUrl: addressBarDialog.metaverseServerUrl; + filter: addressLine.text; } } @@ -326,7 +326,6 @@ StackView { } } - property alias suggestions: feed.suggestions; function getRequest(url, cb) { // cb(error, responseOfCorrectContentType) of url. General for 'get' text/html/json, but without redirects. // TODO: make available to other .qml. @@ -372,122 +371,10 @@ StackView { return true; } - - function resolveUrl(url) { - return (url.indexOf('/') === 0) ? (addressBarDialog.metaverseServerUrl + url) : url; - } - - function makeModelData(data) { // create a new obj from data - // ListModel elements will only ever have those properties that are defined by the first obj that is added. - // So here we make sure that we have all the properties we need, regardless of whether it is a place data or user story. - var name = data.place_name, - tags = data.tags || [data.action, data.username], - description = data.description || "", - thumbnail_url = data.thumbnail_url || ""; - return { - place_name: name, - username: data.username || "", - path: data.path || "", - created_at: data.created_at || "", - action: data.action || "", - thumbnail_url: resolveUrl(thumbnail_url), - image_url: resolveUrl(data.details.image_url), - - metaverseId: (data.id || "").toString(), // Some are strings from server while others are numbers. Model objects require uniformity. - - tags: tags, - description: description, - online_users: data.details.concurrency || 0, - drillDownToPlace: false, - - searchText: [name].concat(tags, description || []).join(' ').toUpperCase() - } - } - function suggestable(place) { - if (place.action === 'snapshot') { - return true; - } - return (place.place_name !== AddressManager.placename); // Not our entry, but do show other entry points to current domain. - } property var selectedTab: allTab; function tabSelect(textButton) { selectedTab = textButton; - fillDestinations(); - } - property var placeMap: ({}); - function addToSuggestions(place) { - var collapse = allTab.selected && (place.action !== 'concurrency'); - if (collapse) { - var existing = placeMap[place.place_name]; - if (existing) { - existing.drillDownToPlace = true; - return; - } - } - suggestions.append(place); - if (collapse) { - placeMap[place.place_name] = suggestions.get(suggestions.count - 1); - } else if (place.action === 'concurrency') { - suggestions.get(suggestions.count - 1).drillDownToPlace = true; // Don't change raw place object (in allStories). - } - } - property int requestId: 0; - function getUserStoryPage(pageNumber, cb) { // cb(error) after all pages of domain data have been added to model - var options = [ - 'now=' + new Date().toISOString(), - 'include_actions=' + selectedTab.includeActions, - 'restriction=' + (Account.isLoggedIn() ? 'open,hifi' : 'open'), - 'require_online=true', - 'protocol=' + encodeURIComponent(AddressManager.protocolVersion()), - 'page=' + pageNumber - ]; - var url = metaverseBase + 'user_stories?' + options.join('&'); - var thisRequestId = ++requestId; - getRequest(url, function (error, data) { - if ((thisRequestId !== requestId) || handleError(url, error, data, cb)) { - return; - } - var stories = data.user_stories.map(function (story) { // explicit single-argument function - return makeModelData(story, url); - }); - allStories = allStories.concat(stories); - stories.forEach(makeFilteredPlaceProcessor()); - if ((data.current_page < data.total_pages) && (data.current_page <= 10)) { // just 10 pages = 100 stories for now - return getUserStoryPage(pageNumber + 1, cb); - } - cb(); - }); - } - function makeFilteredPlaceProcessor() { // answer a function(placeData) that adds it to suggestions if it matches - var words = addressLine.text.toUpperCase().split(/\s+/).filter(identity), - data = allStories; - function matches(place) { - if (!words.length) { - return suggestable(place); - } - return words.every(function (word) { - return place.searchText.indexOf(word) >= 0; - }); - } - return function (place) { - if (matches(place)) { - addToSuggestions(place); - } - }; - } - function filterChoicesByText() { - suggestions.clear(); - placeMap = {}; - allStories.forEach(makeFilteredPlaceProcessor()); - } - - function fillDestinations() { - allStories = []; - suggestions.clear(); - placeMap = {}; - getUserStoryPage(1, function (error) { - console.log('user stories query', error || 'ok', allStories.length); - }); + feed.fillDestinations(); } function updateLocationText(enteringAddress) { From 2512255dd05efd186d686e72685e3d53580f77e9 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 19 Apr 2017 16:33:02 -0700 Subject: [PATCH 10/76] reusable feeds --- interface/resources/qml/hifi/Feed.qml | 68 ++++++++----------- .../qml/hifi/tablet/TabletAddressDialog.qml | 21 +++++- 2 files changed, 46 insertions(+), 43 deletions(-) diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index 2a7156b31e..a1bd38c281 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -17,19 +17,22 @@ import QtGraphicalEffects 1.0 import "toolbars" import "../styles-uit" -Rectangle { +Column { id: root; property int cardWidth: 212; property int cardHeight: 152; property int stackedCardShadowHeight: 10; property string metaverseServerUrl: ''; + property string actions: 'snapshot'; + onActionsChanged: fillDestinations(); + Component.onCompleted: fillDestinations(); + property string labelText: actions; property string filter: ''; onFilterChanged: filterChoicesByText(); - property alias suggestions: feed; // fixme. don't need to expose HifiConstants { id: hifi } - ListModel { id: feed; } + ListModel { id: suggestions; } function resolveUrl(url) { return (url.indexOf('/') === 0) ? (metaverseServerUrl + url) : url; @@ -66,7 +69,7 @@ Rectangle { function getUserStoryPage(pageNumber, cb) { // cb(error) after all pages of domain data have been added to model var options = [ 'now=' + new Date().toISOString(), - 'include_actions=' + selectedTab.includeActions, + 'include_actions=' + actions, 'restriction=' + (Account.isLoggedIn() ? 'open,hifi' : 'open'), 'require_online=true', 'protocol=' + encodeURIComponent(AddressManager.protocolVersion()), @@ -78,11 +81,7 @@ Rectangle { if ((thisRequestId !== requestId) || handleError(url, error, data, cb)) { return; // abandon stale requests } - var stories = data.user_stories.map(function (story) { // explicit single-argument function - return makeModelData(story, url); - }); - allStories = allStories.concat(stories); - stories.forEach(makeFilteredPlaceProcessor()); + allStories = allStories.concat(data.user_stories.map(makeModelData)); if ((data.current_page < data.total_pages) && (data.current_page <= 10)) { // just 10 pages = 100 stories for now return getUserStoryPage(pageNumber + 1, cb); } @@ -94,11 +93,12 @@ Rectangle { suggestions.clear(); placeMap = {}; getUserStoryPage(1, function (error) { - console.log('user stories query', error || 'ok', allStories.length); + allStories.forEach(makeFilteredStoryProcessor()); + console.log('user stories query', actions, error || 'ok', allStories.length, 'filtered to', suggestions.count); }); } - function addToSuggestions(place) { // fixme: move to makeFilteredPlaceProcessor - var collapse = allTab.selected && (place.action !== 'concurrency'); + function addToSuggestions(place) { // fixme: move to makeFilteredStoryProcessor + var collapse = (actions === 'snapshot,concurrency') && (place.action !== 'concurrency'); // fixme generalize? if (collapse) { var existing = placeMap[place.place_name]; if (existing) { @@ -113,61 +113,49 @@ Rectangle { suggestions.get(suggestions.count - 1).drillDownToPlace = true; // Don't change raw place object (in allStories). } } - function suggestable(place) { // fixme add to makeFilteredPlaceProcessor - if (place.action === 'snapshot') { + function suggestable(story) { // fixme add to makeFilteredStoryProcessor + if (story.action === 'snapshot') { return true; } - return (place.place_name !== AddressManager.placename); // Not our entry, but do show other entry points to current domain. + return (story.place_name !== AddressManager.placename); // Not our entry, but do show other entry points to current domain. } - function makeFilteredPlaceProcessor() { // answer a function(placeData) that adds it to suggestions if it matches - var words = filter.toUpperCase().split(/\s+/).filter(identity), - data = allStories; - function matches(place) { + function makeFilteredStoryProcessor() { // answer a function(storyData) that adds it to suggestions if it matches + var words = filter.toUpperCase().split(/\s+/).filter(identity); + function matches(story) { if (!words.length) { - return suggestable(place); + return suggestable(story); } return words.every(function (word) { - return place.searchText.indexOf(word) >= 0; + return story.searchText.indexOf(word) >= 0; }); } - return function (place) { - if (matches(place)) { - addToSuggestions(place); + return function (story) { + if (matches(story)) { + addToSuggestions(story); } }; } function filterChoicesByText() { suggestions.clear(); placeMap = {}; - allStories.forEach(makeFilteredPlaceProcessor()); + allStories.forEach(makeFilteredStoryProcessor()); } RalewayLight { id: label; - text: "Places"; + text: labelText; color: hifi.colors.blueHighlight; - size: 38; - width: root.width; - anchors { - top: parent.top; - left: parent.left; - margins: 10; - } + size: 28; } ListView { id: scroll; clip: true; - model: feed; + model: suggestions; orientation: ListView.Horizontal; spacing: 14; + width: parent.width; height: cardHeight + stackedCardShadowHeight; - anchors { - top: label.bottom; - left: parent.left; - right: parent.right; - leftMargin: 10; - } delegate: Card { width: cardWidth; height: cardHeight; diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index 57e24c2421..e79855ec51 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -39,7 +39,6 @@ StackView { Component { id: tabletWebView; TabletWebView {} } Component.onCompleted: { updateLocationText(false); - feed.fillDestinations(); addressLine.focus = !HMD.active; root.parentChanged.connect(center); center(); @@ -286,11 +285,28 @@ StackView { topMargin: 0; } Feed { - id: feed; + id: happeningNow; width: bgMain.width; metaverseServerUrl: addressBarDialog.metaverseServerUrl; + actions: selectedTab.includeActions; filter: addressLine.text; } + Feed { + id: places; + width: bgMain.width; + metaverseServerUrl: addressBarDialog.metaverseServerUrl; + actions: 'concurrency'; + filter: addressLine.text; + anchors.top: happeningNow.bottom; + } + Feed { + id: snapshots; + width: bgMain.width; + metaverseServerUrl: addressBarDialog.metaverseServerUrl; + actions: 'snapshot'; + filter: addressLine.text; + anchors.top: places.bottom; + } } Timer { @@ -374,7 +390,6 @@ StackView { property var selectedTab: allTab; function tabSelect(textButton) { selectedTab = textButton; - feed.fillDestinations(); } function updateLocationText(enteringAddress) { From b8e9fb67b8e991a25b9a064ad0b86006f4eb639d Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 19 Apr 2017 16:34:10 -0700 Subject: [PATCH 11/76] old style card fonts, margins, etc. --- interface/resources/qml/hifi/Card.qml | 120 ++++++++++---------------- 1 file changed, 46 insertions(+), 74 deletions(-) diff --git a/interface/resources/qml/hifi/Card.qml b/interface/resources/qml/hifi/Card.qml index b72901fbdf..c6e833826a 100644 --- a/interface/resources/qml/hifi/Card.qml +++ b/interface/resources/qml/hifi/Card.qml @@ -80,7 +80,7 @@ Rectangle { id: lobby; visible: !hasGif || (animation.status !== Image.Ready); width: parent.width - (isConcurrency ? 0 : (2 * smallMargin)); - height: parent.height - (isConcurrency ? 0 : smallMargin); + height: parent.height - messageHeight - (isConcurrency ? 0 : smallMargin); source: thumbnail || defaultThumbnail; fillMode: Image.PreserveAspectCrop; anchors { @@ -129,7 +129,7 @@ Rectangle { property int dropSamples: 9; property int dropSpread: 0; DropShadow { - visible: true; + visible: showPlace && desktop.gradientsSupported; source: place; anchors.fill: place; horizontalOffset: dropHorizontalOffset; @@ -139,12 +139,12 @@ Rectangle { color: hifi.colors.black; spread: dropSpread; } - RalewayLight { + RalewaySemiBold { id: place; visible: showPlace; text: placeName; color: hifi.colors.white; - size: 38; + size: textSize; elide: Text.ElideRight; // requires constrained width anchors { top: parent.top; @@ -153,57 +153,44 @@ Rectangle { margins: textPadding; } } - Rectangle { - id: rectRow - z: 1 - width: message.width + (users.visible ? users.width + bottomRow.spacing : 0) - + (icon.visible ? icon.width + bottomRow.spacing: 0) + bottomRow.spacing; - height: messageHeight + 1; - radius: 25 - - anchors { - bottom: parent.bottom - left: parent.left - leftMargin: textPadding - bottomMargin: textPadding + Row { + FiraSansRegular { + id: users; + visible: isConcurrency; + text: onlineUsers; + size: textSize; + color: messageColor; + anchors.verticalCenter: message.verticalCenter; } - - Row { - id: bottomRow - FiraSansRegular { - id: users; - visible: isConcurrency; - text: onlineUsers; - size: textSize; - color: messageColor; - anchors.verticalCenter: message.verticalCenter; - } - Image { - id: icon; - source: "../../images/snap-icon.svg" - width: 40; - height: 40; - visible: action === 'snapshot'; - } - RalewayRegular { - id: message; - text: isConcurrency ? ((onlineUsers === 1) ? "person" : "people") : (drillDownToPlace ? "snapshots" : ("by " + userName)); - size: textSizeSmall; - color: messageColor; - elide: Text.ElideRight; // requires a width to be specified` - anchors { - bottom: parent.bottom; - bottomMargin: parent.spacing; - } - } - spacing: textPadding; - height: messageHeight; + Image { + id: icon; + source: "../../images/snap-icon.svg" + width: 40; + height: 40; + visible: action === 'snapshot'; + } + RalewayRegular { + id: message; + text: isConcurrency ? ((onlineUsers === 1) ? "person" : "people") : (drillDownToPlace ? "snapshots" : ("by " + userName)); + size: textSizeSmall; + color: messageColor; + elide: Text.ElideRight; // requires a width to be specified` + width: root.width - textPadding + - (users.visible ? users.width + parent.spacing : 0) + - (icon.visible ? icon.width + parent.spacing : 0) + - (actionIcon.width + (2 * smallMargin)); anchors { bottom: parent.bottom; - left: parent.left; - leftMargin: 4 + bottomMargin: parent.spacing; } } + spacing: textPadding; + height: messageHeight; + anchors { + bottom: parent.bottom; + left: parent.left; + leftMargin: textPadding; + } } // These two can be supplied to provide hover behavior. // For example, AddressBarDialog provides functions that set the current list view item @@ -218,37 +205,22 @@ Rectangle { onEntered: hoverThunk(); onExited: unhoverThunk(); } - Rectangle { - id: rectIcon - z: 1 - width: 32 - height: 32 - radius: 15 + StateImage { + id: actionIcon; + imageURL: "../../images/info-icon-2-state.svg"; + size: 32; + buttonState: messageArea.containsMouse ? 1 : 0; anchors { bottom: parent.bottom; right: parent.right; - bottomMargin: textPadding; - rightMargin: textPadding; - } - - StateImage { - id: actionIcon; - imageURL: "../../images/info-icon-2-state.svg"; - size: 32; - buttonState: messageArea.containsMouse ? 1 : 0; - anchors { - bottom: parent.bottom; - right: parent.right; - //margins: smallMargin; - } + margins: smallMargin; } } - MouseArea { id: messageArea; - width: rectIcon.width; - height: rectIcon.height; - anchors.fill: rectIcon + width: parent.width; + height: messageHeight; + anchors.top: lobby.bottom; acceptedButtons: Qt.LeftButton; onClicked: goFunction(drillDownToPlace ? ("/places/" + placeName) : ("/user_stories/" + storyId)); hoverEnabled: true; From bc77eeeabdee888d815face0961c057b599dc43c Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Thu, 20 Apr 2017 13:43:55 -0700 Subject: [PATCH 12/76] scroll, margins --- interface/resources/qml/hifi/Feed.qml | 5 +- .../qml/hifi/tablet/TabletAddressDialog.qml | 134 +++++++----------- 2 files changed, 55 insertions(+), 84 deletions(-) diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index a1bd38c281..9f2dfcd553 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -144,7 +144,7 @@ Column { RalewayLight { id: label; text: labelText; - color: hifi.colors.blueHighlight; + color: hifi.colors.white; size: 28; } ListView { @@ -152,6 +152,9 @@ Column { clip: true; model: suggestions; orientation: ListView.Horizontal; + highlightMoveDuration: -1; + highlightMoveVelocity: -1; + highlight: Rectangle { color: "transparent"; border.width: 4; border.color: hifiStyleConstants.colors.blueHighlight; z: 1; } spacing: 14; width: parent.width; diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index e79855ec51..1d07625e34 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -221,91 +221,64 @@ StackView { } } } - Rectangle { - id: topBar - height: 37 - color: hifiStyleConstants.colors.white - - anchors.right: parent.right - anchors.rightMargin: 0 - anchors.left: parent.left - anchors.leftMargin: 0 - anchors.topMargin: 0 - anchors.top: addressBar.bottom - - Row { - id: thing - spacing: 5 * hifi.layout.spacing - - anchors { - top: parent.top; - left: parent.left - leftMargin: 25 - } - - TabletTextButton { - id: allTab; - text: "ALL"; - property string includeActions: 'snapshot,concurrency'; - selected: allTab === selectedTab; - action: tabSelect; - } - - TabletTextButton { - id: placeTab; - text: "PLACES"; - property string includeActions: 'concurrency'; - selected: placeTab === selectedTab; - action: tabSelect; - - } - - TabletTextButton { - id: snapTab; - text: "SNAP"; - property string includeActions: 'snapshot'; - selected: snapTab === selectedTab; - action: tabSelect; - } - } - - } Rectangle { id: bgMain; - color: hifiStyleConstants.colors.white; + color: hifiStyleConstants.colors.faintGray50; anchors { + top: addressBar.bottom; bottom: parent.keyboardEnabled ? keyboard.top : parent.bottom; - bottomMargin: 0; - right: parent.right; - rightMargin: 0; left: parent.left; - leftMargin: 0; - top: topBar.bottom; - topMargin: 0; + right: parent.right; } - Feed { - id: happeningNow; - width: bgMain.width; - metaverseServerUrl: addressBarDialog.metaverseServerUrl; - actions: selectedTab.includeActions; - filter: addressLine.text; - } - Feed { - id: places; - width: bgMain.width; - metaverseServerUrl: addressBarDialog.metaverseServerUrl; - actions: 'concurrency'; - filter: addressLine.text; - anchors.top: happeningNow.bottom; - } - Feed { - id: snapshots; - width: bgMain.width; - metaverseServerUrl: addressBarDialog.metaverseServerUrl; - actions: 'snapshot'; - filter: addressLine.text; - anchors.top: places.bottom; + ScrollView { + anchors.fill: bgMain; + horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff; + verticalScrollBarPolicy: Qt.ScrollBarAlwaysOn; //Qt.ScrollBarAsNeeded; + Rectangle { // Column margins require QtQuick 2.7, which we don't use yet. + id: column; + property real pad: 10; + width: bgMain.width - column.pad; + height: stack.height; + color: "transparent"; + anchors { + left: bgMain.left; + leftMargin: column.pad; + topMargin: column.pad; + } + Column { + id: stack; + width: column.width; + spacing: column.pad; + Feed { + id: happeningNow; + width: parent.width; + property real cardScale: 1.5; + cardWidth: places.cardWidth * happeningNow.cardScale; + cardHeight: places.cardHeight * happeningNow.cardScale; + metaverseServerUrl: addressBarDialog.metaverseServerUrl; + labelText: 'Happening Now'; + actions: 'concurrency,snapshot'; //selectedTab.includeActions; + filter: addressLine.text; + } + Feed { + id: places; + width: parent.width; + metaverseServerUrl: addressBarDialog.metaverseServerUrl; + labelText: 'Places'; + actions: 'concurrency'; + filter: addressLine.text; + } + Feed { + id: snapshots; + width: parent.width; + metaverseServerUrl: addressBarDialog.metaverseServerUrl; + labelText: 'Recent Activity'; + actions: 'snapshot'; + filter: addressLine.text; + } + } + } } } @@ -387,11 +360,6 @@ StackView { return true; } - property var selectedTab: allTab; - function tabSelect(textButton) { - selectedTab = textButton; - } - function updateLocationText(enteringAddress) { if (enteringAddress) { notice.text = "Go To a place, @user, path, or network address:"; From ce7145a3884ad1633fb73c64c201eb96357e526e Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Thu, 20 Apr 2017 14:40:24 -0700 Subject: [PATCH 13/76] Announcement cards, debug data. --- interface/resources/qml/hifi/Card.qml | 5 +- interface/resources/qml/hifi/Feed.qml | 55 +++++++++++-------- .../qml/hifi/tablet/TabletAddressDialog.qml | 6 +- 3 files changed, 37 insertions(+), 29 deletions(-) diff --git a/interface/resources/qml/hifi/Card.qml b/interface/resources/qml/hifi/Card.qml index c6e833826a..1a3ae43692 100644 --- a/interface/resources/qml/hifi/Card.qml +++ b/interface/resources/qml/hifi/Card.qml @@ -35,6 +35,7 @@ Rectangle { property string timePhrase: pastTime(timestamp); property int onlineUsers: 0; property bool isConcurrency: action === 'concurrency'; + property bool isAnnouncement: action === 'announcement'; property bool isStacked: !isConcurrency && drillDownToPlace; property int textPadding: 10; @@ -156,7 +157,7 @@ Rectangle { Row { FiraSansRegular { id: users; - visible: isConcurrency; + visible: isConcurrency || isAnnouncement; text: onlineUsers; size: textSize; color: messageColor; @@ -171,7 +172,7 @@ Rectangle { } RalewayRegular { id: message; - text: isConcurrency ? ((onlineUsers === 1) ? "person" : "people") : (drillDownToPlace ? "snapshots" : ("by " + userName)); + text: isConcurrency ? ((onlineUsers === 1) ? "person" : "people") : (isAnnouncement ? "connections" : (drillDownToPlace ? "snapshots" : ("by " + userName))); size: textSizeSmall; color: messageColor; elide: Text.ElideRight; // requires a width to be specified` diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index 9f2dfcd553..4f73d5bd16 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -19,6 +19,7 @@ import "../styles-uit" Column { id: root; + visible: false; property int cardWidth: 212; property int cardHeight: 152; @@ -44,6 +45,11 @@ Column { tags = data.tags || [data.action, data.username], description = data.description || "", thumbnail_url = data.thumbnail_url || ""; + if (actions === 'concurrency,snapshot') { + // A temporary hack for simulating announcements. We won't use this in production, but if requested, we'll use this data like announcements. + data.details.connections = 4; + data.action = 'announcement'; + } return { place_name: name, username: data.username || "", @@ -57,14 +63,14 @@ Column { tags: tags, description: description, - online_users: data.details.concurrency || 0, + online_users: data.details.connections || data.details.concurrency || 0, drillDownToPlace: false, searchText: [name].concat(tags, description || []).join(' ').toUpperCase() } } property var allStories: []; - property var placeMap: ({}); // Used for making stacks. FIXME: generalize to not just by place. + property var placeMap: ({}); // Used for making stacks. property int requestId: 0; function getUserStoryPage(pageNumber, cb) { // cb(error) after all pages of domain data have been added to model var options = [ @@ -95,32 +101,17 @@ Column { getUserStoryPage(1, function (error) { allStories.forEach(makeFilteredStoryProcessor()); console.log('user stories query', actions, error || 'ok', allStories.length, 'filtered to', suggestions.count); + root.visible = !!suggestions.count; // fixme reset on filtering, too! }); } - function addToSuggestions(place) { // fixme: move to makeFilteredStoryProcessor - var collapse = (actions === 'snapshot,concurrency') && (place.action !== 'concurrency'); // fixme generalize? - if (collapse) { - var existing = placeMap[place.place_name]; - if (existing) { - existing.drillDownToPlace = true; - return; - } - } - suggestions.append(place); - if (collapse) { - placeMap[place.place_name] = suggestions.get(suggestions.count - 1); - } else if (place.action === 'concurrency') { - suggestions.get(suggestions.count - 1).drillDownToPlace = true; // Don't change raw place object (in allStories). - } - } - function suggestable(story) { // fixme add to makeFilteredStoryProcessor - if (story.action === 'snapshot') { - return true; - } - return (story.place_name !== AddressManager.placename); // Not our entry, but do show other entry points to current domain. - } function makeFilteredStoryProcessor() { // answer a function(storyData) that adds it to suggestions if it matches var words = filter.toUpperCase().split(/\s+/).filter(identity); + function suggestable(story) { // fixme add to makeFilteredStoryProcessor + if (story.action === 'snapshot') { + return true; + } + return (story.place_name !== AddressManager.placename); // Not our entry, but do show other entry points to current domain. + } function matches(story) { if (!words.length) { return suggestable(story); @@ -129,6 +120,22 @@ Column { return story.searchText.indexOf(word) >= 0; }); } + function addToSuggestions(place) { + var collapse = ((actions === 'concurrency,snapshot') && (place.action !== 'concurrency')) || (place.action === 'announcement'); + if (collapse) { + var existing = placeMap[place.place_name]; + if (existing) { + existing.drillDownToPlace = true; + return; + } + } + suggestions.append(place); + if (collapse) { + placeMap[place.place_name] = suggestions.get(suggestions.count - 1); + } else if (place.action === 'concurrency') { + suggestions.get(suggestions.count - 1).drillDownToPlace = true; // Don't change raw place object (in allStories). + } + } return function (story) { if (matches(story)) { addToSuggestions(story); diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index 1d07625e34..5be8e158f4 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -186,7 +186,6 @@ StackView { } font.pixelSize: hifi.fonts.pixelSize * 0.75 onTextChanged: { - console.log('fixme onTextChanged "' + addressLine.text + "'."); updateLocationText(text.length > 0); } onAccepted: { @@ -234,7 +233,7 @@ StackView { ScrollView { anchors.fill: bgMain; horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff; - verticalScrollBarPolicy: Qt.ScrollBarAlwaysOn; //Qt.ScrollBarAsNeeded; + verticalScrollBarPolicy: Qt.ScrollBarAsNeeded; Rectangle { // Column margins require QtQuick 2.7, which we don't use yet. id: column; property real pad: 10; @@ -258,7 +257,8 @@ StackView { cardHeight: places.cardHeight * happeningNow.cardScale; metaverseServerUrl: addressBarDialog.metaverseServerUrl; labelText: 'Happening Now'; - actions: 'concurrency,snapshot'; //selectedTab.includeActions; + actions: 'concurrency,snapshot'; // uncomment this line instead of next to produce fake announcement data for testing. + //actions: 'announcement'; filter: addressLine.text; } Feed { From 4ea882cad13fbc52e74cab5c3600655290a4e185 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Thu, 20 Apr 2017 14:56:34 -0700 Subject: [PATCH 14/76] faster initial response --- interface/resources/qml/hifi/Feed.qml | 18 +++++++++++++++--- .../qml/hifi/tablet/TabletAddressDialog.qml | 4 ++-- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index 4f73d5bd16..c02cd1dd36 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -72,7 +72,8 @@ Column { property var allStories: []; property var placeMap: ({}); // Used for making stacks. property int requestId: 0; - function getUserStoryPage(pageNumber, cb) { // cb(error) after all pages of domain data have been added to model + function getUserStoryPage(pageNumber, cb, cb1) { // cb(error) after all pages of domain data have been added to model + // If supplied, cb1 will be run after the first page IFF it is not the last, for responsiveness. var options = [ 'now=' + new Date().toISOString(), 'include_actions=' + actions, @@ -89,19 +90,29 @@ Column { } allStories = allStories.concat(data.user_stories.map(makeModelData)); if ((data.current_page < data.total_pages) && (data.current_page <= 10)) { // just 10 pages = 100 stories for now + if ((pageNumber === 1) && cb1) { + cb1(); + } return getUserStoryPage(pageNumber + 1, cb); } cb(); }); } function fillDestinations() { // Public + var filter = makeFilteredStoryProcessor(), counter = 0; allStories = []; suggestions.clear(); placeMap = {}; getUserStoryPage(1, function (error) { - allStories.forEach(makeFilteredStoryProcessor()); + allStories.slice(counter).forEach(filter); console.log('user stories query', actions, error || 'ok', allStories.length, 'filtered to', suggestions.count); - root.visible = !!suggestions.count; // fixme reset on filtering, too! + root.visible = !!suggestions.count; + }, function () { // If there's more than a page, put what we have in the model right away, keeping track of how many are processed. + allStories.forEach(function (story) { + counter++; + filter(story); + root.visible = !!suggestions.count; + }); }); } function makeFilteredStoryProcessor() { // answer a function(storyData) that adds it to suggestions if it matches @@ -146,6 +157,7 @@ Column { suggestions.clear(); placeMap = {}; allStories.forEach(makeFilteredStoryProcessor()); + root.visible = !!suggestions.count; } RalewayLight { diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index 5be8e158f4..02881f14c5 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -257,8 +257,8 @@ StackView { cardHeight: places.cardHeight * happeningNow.cardScale; metaverseServerUrl: addressBarDialog.metaverseServerUrl; labelText: 'Happening Now'; - actions: 'concurrency,snapshot'; // uncomment this line instead of next to produce fake announcement data for testing. - //actions: 'announcement'; + //actions: 'concurrency,snapshot'; // uncomment this line instead of next to produce fake announcement data for testing. + actions: 'announcement'; filter: addressLine.text; } Feed { From ce7f3197f679eafc367134419b5ecb54107ad66b Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Fri, 21 Apr 2017 14:27:38 -0700 Subject: [PATCH 15/76] watch for announcements --- .../scripting/WindowScriptingInterface.cpp | 4 + .../src/scripting/WindowScriptingInterface.h | 2 + scripts/system/notifications.js | 5 +- scripts/system/tablet-goto.js | 109 +++++++++++++++++- 4 files changed, 116 insertions(+), 4 deletions(-) diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index 39c2f2e402..6256bd456d 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -243,6 +243,10 @@ void WindowScriptingInterface::makeConnection(bool success, const QString& userN } } +void WindowScriptingInterface::displayAnnouncement(const QString& message) { + emit announcement(message); +} + bool WindowScriptingInterface::isPhysicsEnabled() { return qApp->isPhysicsEnabled(); } diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index d4ff278fea..99ccf4eed0 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -57,6 +57,7 @@ public slots: void copyToClipboard(const QString& text); void takeSnapshot(bool notify = true, bool includeAnimated = false, float aspectRatio = 0.0f); void makeConnection(bool success, const QString& userNameOrError); + void displayAnnouncement(const QString& message); void shareSnapshot(const QString& path, const QUrl& href = QUrl("")); bool isPhysicsEnabled(); @@ -78,6 +79,7 @@ signals: void connectionAdded(const QString& connectionName); void connectionError(const QString& errorString); + void announcement(const QString& message); void messageBoxClosed(int id, int button); diff --git a/scripts/system/notifications.js b/scripts/system/notifications.js index 006ef3f90f..c08cb44c0c 100644 --- a/scripts/system/notifications.js +++ b/scripts/system/notifications.js @@ -188,10 +188,10 @@ function fadeOut(noticeOut, buttonOut, arraysOut) { pauseTimer = Script.setInterval(function () { r -= 1; - rFade = r / 10.0; + rFade = Math.max(0.0, r / 10.0); Overlays.editOverlay(noticeOut, { alpha: rFade }); Overlays.editOverlay(buttonOut, { alpha: rFade }); - if (r < 0) { + if (r <= 0) { dismiss(noticeOut, buttonOut, arraysOut); arrays.splice(arraysOut, 1); ready = true; @@ -660,6 +660,7 @@ Window.stillSnapshotTaken.connect(onSnapshotTaken); Window.processingGifStarted.connect(processingGif); Window.connectionAdded.connect(connectionAdded); Window.connectionError.connect(connectionError); +Window.announcement.connect(onNotify); Window.notifyEditError = onEditError; Window.notify = onNotify; Tablet.tabletNotification.connect(tabletNotification); diff --git a/scripts/system/tablet-goto.js b/scripts/system/tablet-goto.js index 84f7357b1a..3bb8c746c7 100644 --- a/scripts/system/tablet-goto.js +++ b/scripts/system/tablet-goto.js @@ -33,6 +33,7 @@ onGotoScreen = true; shouldActivateButton = true; button.editProperties({isActive: shouldActivateButton}); + messagesWaiting(false); } else { shouldActivateButton = false; onGotoScreen = false; @@ -41,17 +42,121 @@ } var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + var NORMAL_ICON = "icons/tablet-icons/goto-i.svg"; + var NORMAL_ACTIVE = "icons/tablet-icons/goto-a.svg"; + var WAITING_ICON = "icons/tablet-icons/help-i.svg"; // To be changed when we get the artwork. + var WAITING_ACTIVE = "icons/tablet-icons/help-a.svg"; var button = tablet.addButton({ - icon: "icons/tablet-icons/goto-i.svg", - activeIcon: "icons/tablet-icons/goto-a.svg", + icon: NORMAL_ICON, + activeIcon: NORMAL_ACTIVE, text: buttonName, sortOrder: 8 }); + function messagesWaiting(isWaiting) { + button.editProperties({ + icon: isWaiting ? WAITING_ICON : NORMAL_ICON, + activeIcon: isWaiting ? WAITING_ACTIVE : NORMAL_ACTIVE + }); + } button.clicked.connect(onClicked); tablet.screenChanged.connect(onScreenChanged); + var METAVERSE_BASE = location.metaverseServerUrl; + function request(options, callback) { // cb(error, responseOfCorrectContentType) of url. A subset of npm request. + var httpRequest = new XMLHttpRequest(), key; + // QT bug: apparently doesn't handle onload. Workaround using readyState. + httpRequest.onreadystatechange = function () { + var READY_STATE_DONE = 4; + var HTTP_OK = 200; + if (httpRequest.readyState >= READY_STATE_DONE) { + var error = (httpRequest.status !== HTTP_OK) && httpRequest.status.toString() + ':' + httpRequest.statusText, + response = !error && httpRequest.responseText, + contentType = !error && httpRequest.getResponseHeader('content-type'); + if (!error && contentType.indexOf('application/json') === 0) { // ignoring charset, etc. + try { + response = JSON.parse(response); + } catch (e) { + error = e; + } + } + callback(error, response); + } + }; + if (typeof options === 'string') { + options = {uri: options}; + } + if (options.url) { + options.uri = options.url; + } + if (!options.method) { + options.method = 'GET'; + } + if (options.body && (options.method === 'GET')) { // add query parameters + var params = [], appender = (-1 === options.uri.search('?')) ? '?' : '&'; + for (key in options.body) { + params.push(key + '=' + options.body[key]); + } + options.uri += appender + params.join('&'); + delete options.body; + } + if (options.json) { + options.headers = options.headers || {}; + options.headers["Content-type"] = "application/json"; + options.body = JSON.stringify(options.body); + } + for (key in options.headers || {}) { + httpRequest.setRequestHeader(key, options.headers[key]); + } + httpRequest.open(options.method, options.uri, true); + httpRequest.send(options.body); + } + + var stories = {}; + var DEBUG = false; + function pollForAnnouncements() { + var actions = DEBUG ? 'snapshot' : 'announcement'; + var count = DEBUG ? 10 : 100; + var options = [ + 'now=' + new Date().toISOString(), + 'include_actions=' + actions, + 'restriction=' + (Account.isLoggedIn() ? 'open,hifi' : 'open'), + 'require_online=true', + 'protocol=' + encodeURIComponent(location.protocolVersion()), + 'per_page=' + count + ]; + var url = location.metaverseServerUrl + '/api/v1/user_stories?' + options.join('&'); + request({ + uri: url + }, function (error, data) { + if (error || (data.status !== 'success')) { + print("Error: unable to get", url, error || response.status); + return; + } + var didNotify = false; + data.user_stories.forEach(function (story) { + if (stories[story.id]) { // already seen + return; + } + stories[story.id] = story; + var message = story.username + " says something is happending in " + story.place_name + ". Open GOTO to join them."; + Window.displayAnnouncement(message); + didNotify = true; + }); + if (didNotify) { + messagesWaiting(true); + if (HMD.isHandControllerAvailable()) { + var STRENGTH = 1.0, DURATION_MS = 60, HAND = 2; // both hands + Controller.triggerHapticPulse(STRENGTH, DURATION_MS, HAND); + } + } + }); + } + var ANNOUNCEMENTS_POLL_TIME_MS = (DEBUG ? 10 : 60) * 1000; + var pollTimer = Script.setInterval(pollForAnnouncements, ANNOUNCEMENTS_POLL_TIME_MS); + Script.scriptEnding.connect(function () { + Script.clearInterval(pollTimer); button.clicked.disconnect(onClicked); tablet.removeButton(button); tablet.screenChanged.disconnect(onScreenChanged); From fac40f51d9fc8536d552c43106db4ca41bb3449b Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Fri, 21 Apr 2017 16:00:23 -0700 Subject: [PATCH 16/76] message waiting art --- .../resources/icons/tablet-icons/goto-msg.svg | 18 ++++++++++++++++++ scripts/system/tablet-goto.js | 7 +++---- 2 files changed, 21 insertions(+), 4 deletions(-) create mode 100644 interface/resources/icons/tablet-icons/goto-msg.svg diff --git a/interface/resources/icons/tablet-icons/goto-msg.svg b/interface/resources/icons/tablet-icons/goto-msg.svg new file mode 100644 index 0000000000..9b576ab1bf --- /dev/null +++ b/interface/resources/icons/tablet-icons/goto-msg.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + diff --git a/scripts/system/tablet-goto.js b/scripts/system/tablet-goto.js index 3bb8c746c7..bf4f79a346 100644 --- a/scripts/system/tablet-goto.js +++ b/scripts/system/tablet-goto.js @@ -44,8 +44,7 @@ var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); var NORMAL_ICON = "icons/tablet-icons/goto-i.svg"; var NORMAL_ACTIVE = "icons/tablet-icons/goto-a.svg"; - var WAITING_ICON = "icons/tablet-icons/help-i.svg"; // To be changed when we get the artwork. - var WAITING_ACTIVE = "icons/tablet-icons/help-a.svg"; + var WAITING_ICON = "icons/tablet-icons/goto-msg.svg"; var button = tablet.addButton({ icon: NORMAL_ICON, activeIcon: NORMAL_ACTIVE, @@ -54,8 +53,8 @@ }); function messagesWaiting(isWaiting) { button.editProperties({ - icon: isWaiting ? WAITING_ICON : NORMAL_ICON, - activeIcon: isWaiting ? WAITING_ACTIVE : NORMAL_ACTIVE + icon: isWaiting ? WAITING_ICON : NORMAL_ICON + // No need for a different activeIcon, because we issue messagesWaiting(false) when the button goes active anyway. }); } From 47e51493e87cc3d36906bdf67e2d8079133c602e Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 24 Apr 2017 09:56:10 -0700 Subject: [PATCH 17/76] dynamicallyAdjustLimits on the underPoses not the relaxed poses. --- libraries/animation/src/AnimInverseKinematics.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 0e7a41e74c..a457adddd5 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -426,13 +426,13 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars _relativePoses[i].trans() = underPoses[i].trans(); } - if (!_relativePoses.empty()) { + if (!underPoses.empty()) { // Sometimes the underpose itself can violate the constraints. Rather than // clamp the animation we dynamically expand each constraint to accomodate it. std::map::iterator constraintItr = _constraints.begin(); while (constraintItr != _constraints.end()) { int index = constraintItr->first; - constraintItr->second->dynamicallyAdjustLimits(_relativePoses[index].rot()); + constraintItr->second->dynamicallyAdjustLimits(underPoses[index].rot()); ++constraintItr; } } From dc3803a2250d0c896ddce146729be50687af831e Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 24 Apr 2017 13:59:02 -0700 Subject: [PATCH 18/76] Re-enable IK _hipsOffset computation when no hips IK target is present. --- interface/src/avatar/SkeletonModel.cpp | 6 +- .../animation/src/AnimInverseKinematics.cpp | 140 ++++++++++++++---- .../animation/src/AnimInverseKinematics.h | 2 + libraries/animation/src/Rig.cpp | 32 +++- 4 files changed, 144 insertions(+), 36 deletions(-) diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index e26c339fb8..3cf866fb6b 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -120,8 +120,10 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { headParams.rigHeadOrientation = extractRotation(rigHMDMat); headParams.worldHeadOrientation = extractRotation(worldHMDMat); - headParams.hipsMatrix = worldToRig * myAvatar->getSensorToWorldMatrix() * myAvatar->deriveBodyFromHMDSensor(); - headParams.hipsEnabled = true; + // TODO: if hips target sensor is valid. + // Copy it into headParams.hipsMatrix, and set headParams.hipsEnabled to true. + + headParams.hipsEnabled = false; } else { headParams.hipsEnabled = false; headParams.isInHMD = false; diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index a457adddd5..a9898fb4d2 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -86,6 +86,7 @@ void AnimInverseKinematics::setTargetVars( void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std::vector& targets, const AnimPoseVec& underPoses) { // build a list of valid targets from _targetVarVec and animVars _maxTargetIndex = -1; + _hipsTargetIndex = -1; bool removeUnfoundJoints = false; for (auto& targetVar : _targetVarVec) { @@ -114,6 +115,11 @@ void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std:: if (targetVar.jointIndex > _maxTargetIndex) { _maxTargetIndex = targetVar.jointIndex; } + + // record the index of the hips ik target. + if (target.getIndex() == _hipsIndex) { + _hipsTargetIndex = (int)targets.size() - 1; + } } } } @@ -242,6 +248,8 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe // the tip's parent-relative as we proceed up the chain glm::quat tipParentOrientation = absolutePoses[pivotIndex].rot(); + // AJT: REMOVE + /* if (targetType == IKTarget::Type::HmdHead) { // rotate tip directly to target orientation tipOrientation = target.getRotation(); @@ -259,6 +267,7 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe // store the relative rotation change in the accumulator _accumulators[tipIndex].add(tipRelativeRotation, target.getWeight()); } + */ // cache tip absolute position glm::vec3 tipPosition = absolutePoses[tipIndex].trans(); @@ -278,9 +287,8 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe const float MIN_AXIS_LENGTH = 1.0e-4f; RotationConstraint* constraint = getConstraint(pivotIndex); - // AJT: disabled special case for the lower spine. - /* - if (constraint && constraint->isLowerSpine() && tipIndex != _headIndex) { + // only allow swing on lowerSpine if there is a hips IK target. + if (_hipsTargetIndex < 0 && constraint && constraint->isLowerSpine() && tipIndex != _headIndex) { // for these types of targets we only allow twist at the lower-spine // (this prevents the hand targets from bending the spine too much and thereby driving the hips too far) glm::vec3 twistAxis = absolutePoses[pivotIndex].trans() - absolutePoses[pivotsParentIndex].trans(); @@ -295,7 +303,6 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe targetLine = Vectors::ZERO; } } - */ glm::vec3 axis = glm::cross(leverArm, targetLine); float axisLength = glm::length(axis); @@ -365,9 +372,7 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe } // store the relative rotation change in the accumulator - // AJT: Hack give head more weight. - float weight = (target.getIndex() == _headIndex) ? 2.0f : 1.0f; - _accumulators[pivotIndex].add(newRot, weight); + _accumulators[pivotIndex].add(newRot, target.getWeight()); // this joint has been changed so we check to see if it has the lowest index if (pivotIndex < lowestMovedIndex) { @@ -448,41 +453,46 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars } if (targets.empty()) { - // no IK targets but still need to enforce constraints - std::map::iterator constraintItr = _constraints.begin(); - while (constraintItr != _constraints.end()) { - int index = constraintItr->first; - glm::quat rotation = _relativePoses[index].rot(); - constraintItr->second->apply(rotation); - _relativePoses[index].rot() = rotation; - ++constraintItr; - } + _relativePoses = underPoses; } else { { PROFILE_RANGE_EX(simulation_animation, "ik/shiftHips", 0xffff00ff, 0); - // AJT: TODO only need abs poses below hips. - AnimPoseVec absolutePoses; - absolutePoses.resize(_relativePoses.size()); - computeAbsolutePoses(absolutePoses); - - for (auto& target: targets) { - if (target.getType() == IKTarget::Type::RotationAndPosition && target.getIndex() == _hipsIndex) { - AnimPose absPose = target.getPose(); - int parentIndex = _skeleton->getParentIndex(target.getIndex()); - if (parentIndex != -1) { - _relativePoses[_hipsIndex] = absolutePoses[parentIndex].inverse() * absPose; + if (_hipsTargetIndex >= 0 && _hipsTargetIndex < targets.size()) { + // slam the hips to match the _hipsTarget + AnimPose absPose = targets[_hipsTargetIndex].getPose(); + int parentIndex = _skeleton->getParentIndex(targets[_hipsTargetIndex].getIndex()); + if (parentIndex != -1) { + _relativePoses[_hipsIndex] = _skeleton->getAbsolutePose(parentIndex, _relativePoses).inverse() * absPose; + } else { + _relativePoses[_hipsIndex] = absPose; + } + } else { + // if there is no hips target, shift hips according to the _hipsOffset from the previous frame + float offsetLength = glm::length(_hipsOffset); + const float MIN_HIPS_OFFSET_LENGTH = 0.03f; + if (offsetLength > MIN_HIPS_OFFSET_LENGTH && _hipsIndex >= 0) { + float scaleFactor = ((offsetLength - MIN_HIPS_OFFSET_LENGTH) / offsetLength); + glm::vec3 hipsOffset = scaleFactor * _hipsOffset; + if (_hipsParentIndex == -1) { + _relativePoses[_hipsIndex].trans() = underPoses[_hipsIndex].trans() + hipsOffset; } else { - _relativePoses[_hipsIndex] = absPose; + auto absHipsPose = _skeleton->getAbsolutePose(_hipsIndex, underPoses); + absHipsPose.trans() += hipsOffset; + _relativePoses[_hipsIndex] = _skeleton->getAbsolutePose(_hipsParentIndex, _relativePoses).inverse() * absHipsPose; } } } + // update all HipsRelative targets to account for the hips shift/ik target. + auto shiftedHipsAbsPose = _skeleton->getAbsolutePose(_hipsIndex, _relativePoses); + auto underHipsAbsPose = _skeleton->getAbsolutePose(_hipsIndex, underPoses); + auto absHipsOffset = shiftedHipsAbsPose.trans() - underHipsAbsPose.trans(); for (auto& target: targets) { if (target.getType() == IKTarget::Type::HipsRelativeRotationAndPosition) { - AnimPose pose = target.getPose(); - pose.trans() = pose.trans() + (_relativePoses[_hipsIndex].trans() - underPoses[_hipsIndex].trans()); + auto pose = target.getPose(); + pose.trans() = pose.trans() + absHipsOffset; target.setPose(pose.rot(), pose.trans()); } } @@ -518,11 +528,81 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars PROFILE_RANGE_EX(simulation_animation, "ik/ccd", 0xffff00ff, 0); solveWithCyclicCoordinateDescent(targets); } + + if (_hipsTargetIndex < 0) { + PROFILE_RANGE_EX(simulation_animation, "ik/measureHipsOffset", 0xffff00ff, 0); + computeHipsOffset(targets, underPoses, dt); + } else { + _hipsOffset = Vectors::ZERO; + } } } return _relativePoses; } +void AnimInverseKinematics::computeHipsOffset(const std::vector& targets, const AnimPoseVec& underPoses, float dt) { + // measure new _hipsOffset for next frame + // by looking for discrepancies between where a targeted endEffector is + // and where it wants to be (after IK solutions are done) + + // OUTOFBODY_HACK:use weighted average between HMD and other targets + float HMD_WEIGHT = 10.0f; + float OTHER_WEIGHT = 1.0f; + float totalWeight = 0.0f; + + glm::vec3 additionalHipsOffset = Vectors::ZERO; + for (auto& target: targets) { + int targetIndex = target.getIndex(); + if (targetIndex == _headIndex && _headIndex != -1) { + // special handling for headTarget + if (target.getType() == IKTarget::Type::RotationOnly) { + // we want to shift the hips to bring the underPose closer + // to where the head happens to be (overpose) + glm::vec3 under = _skeleton->getAbsolutePose(_headIndex, underPoses).trans(); + glm::vec3 actual = _skeleton->getAbsolutePose(_headIndex, _relativePoses).trans(); + const float HEAD_OFFSET_SLAVE_FACTOR = 0.65f; + additionalHipsOffset += (OTHER_WEIGHT * HEAD_OFFSET_SLAVE_FACTOR) * (under - actual); + totalWeight += OTHER_WEIGHT; + } else if (target.getType() == IKTarget::Type::HmdHead) { + glm::vec3 actual = _skeleton->getAbsolutePose(_headIndex, _relativePoses).trans(); + glm::vec3 thisOffset = target.getTranslation() - actual; + glm::vec3 futureHipsOffset = _hipsOffset + thisOffset; + if (glm::length(glm::vec2(futureHipsOffset.x, futureHipsOffset.z)) < _maxHipsOffsetLength) { + // it is imperative to shift the hips and bring the head to its designated position + // so we slam newHipsOffset here and ignore all other targets + additionalHipsOffset = futureHipsOffset - _hipsOffset; + totalWeight = 0.0f; + break; + } else { + additionalHipsOffset += HMD_WEIGHT * (target.getTranslation() - actual); + totalWeight += HMD_WEIGHT; + } + } + } else if (target.getType() == IKTarget::Type::RotationAndPosition) { + glm::vec3 actualPosition = _skeleton->getAbsolutePose(targetIndex, _relativePoses).trans(); + glm::vec3 targetPosition = target.getTranslation(); + additionalHipsOffset += OTHER_WEIGHT * (targetPosition - actualPosition); + totalWeight += OTHER_WEIGHT; + } + } + if (totalWeight > 1.0f) { + additionalHipsOffset /= totalWeight; + } + + // smooth transitions by relaxing _hipsOffset toward the new value + const float HIPS_OFFSET_SLAVE_TIMESCALE = 0.10f; + float tau = dt < HIPS_OFFSET_SLAVE_TIMESCALE ? dt / HIPS_OFFSET_SLAVE_TIMESCALE : 1.0f; + _hipsOffset += additionalHipsOffset * tau; + + // clamp the horizontal component of the hips offset + float hipsOffsetLength2D = glm::length(glm::vec2(_hipsOffset.x, _hipsOffset.z)); + if (hipsOffsetLength2D > _maxHipsOffsetLength) { + _hipsOffset.x *= _maxHipsOffsetLength / hipsOffsetLength2D; + _hipsOffset.z *= _maxHipsOffsetLength / hipsOffsetLength2D; + } + +} + void AnimInverseKinematics::setMaxHipsOffsetLength(float maxLength) { // manually adjust scale here const float METERS_TO_CENTIMETERS = 100.0f; diff --git a/libraries/animation/src/AnimInverseKinematics.h b/libraries/animation/src/AnimInverseKinematics.h index 366e5f765e..c91b7aa9c4 100644 --- a/libraries/animation/src/AnimInverseKinematics.h +++ b/libraries/animation/src/AnimInverseKinematics.h @@ -55,6 +55,7 @@ protected: RotationConstraint* getConstraint(int index); void clearConstraints(); void initConstraints(); + void computeHipsOffset(const std::vector& targets, const AnimPoseVec& underPoses, float dt); // no copies AnimInverseKinematics(const AnimInverseKinematics&) = delete; @@ -91,6 +92,7 @@ protected: int _headIndex { -1 }; int _hipsIndex { -1 }; int _hipsParentIndex { -1 }; + int _hipsTargetIndex { -1 }; // _maxTargetIndex is tracked to help optimize the recalculation of absolute poses // during the the cyclic coordinate descent algorithm diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index dd7ccb6b27..116758b1ba 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1025,7 +1025,6 @@ void Rig::updateFromHeadParameters(const HeadParameters& params, float dt) { _animVars.set("isTalking", params.isTalking); _animVars.set("notIsTalking", !params.isTalking); - // AJT: if (params.hipsEnabled) { _animVars.set("hipsType", (int)IKTarget::Type::RotationAndPosition); _animVars.set("hipsPosition", extractTranslation(params.hipsMatrix)); @@ -1106,7 +1105,7 @@ void Rig::updateNeckJoint(int index, const HeadParameters& params) { _animVars.set("headPosition", headPos); _animVars.set("headRotation", headRot); - _animVars.set("headType", (int)IKTarget::Type::RotationAndPosition); + _animVars.set("headType", (int)IKTarget::Type::HmdHead); _animVars.set("neckPosition", neckPos); _animVars.set("neckRotation", neckRot); _animVars.set("neckType", (int)IKTarget::Type::Unknown); // 'Unknown' disables the target @@ -1173,8 +1172,22 @@ void Rig::updateFromHandAndFeetParameters(const HandAndFeetParameters& params, f const glm::vec3 bodyCapsuleStart = bodyCapsuleCenter - glm::vec3(0, params.bodyCapsuleHalfHeight, 0); const glm::vec3 bodyCapsuleEnd = bodyCapsuleCenter + glm::vec3(0, params.bodyCapsuleHalfHeight, 0); + // TODO: add isHipsEnabled + bool bodySensorTrackingEnabled = params.isLeftFootEnabled || params.isRightFootEnabled; + if (params.isLeftEnabled) { - _animVars.set("leftHandPosition", params.leftPosition); + + glm::vec3 handPosition = params.leftPosition; + + if (!bodySensorTrackingEnabled) { + // prevent the hand IK targets from intersecting the body capsule + glm::vec3 displacement; + if (findSphereCapsulePenetration(handPosition, HAND_RADIUS, bodyCapsuleStart, bodyCapsuleEnd, bodyCapsuleRadius, displacement)) { + handPosition -= displacement; + } + } + + _animVars.set("leftHandPosition", handPosition); _animVars.set("leftHandRotation", params.leftOrientation); _animVars.set("leftHandType", (int)IKTarget::Type::RotationAndPosition); } else { @@ -1184,7 +1197,18 @@ void Rig::updateFromHandAndFeetParameters(const HandAndFeetParameters& params, f } if (params.isRightEnabled) { - _animVars.set("rightHandPosition", params.rightPosition); + + glm::vec3 handPosition = params.rightPosition; + + if (!bodySensorTrackingEnabled) { + // prevent the hand IK targets from intersecting the body capsule + glm::vec3 displacement; + if (findSphereCapsulePenetration(handPosition, HAND_RADIUS, bodyCapsuleStart, bodyCapsuleEnd, bodyCapsuleRadius, displacement)) { + handPosition -= displacement; + } + } + + _animVars.set("rightHandPosition", handPosition); _animVars.set("rightHandRotation", params.rightOrientation); _animVars.set("rightHandType", (int)IKTarget::Type::RotationAndPosition); } else { From 1cd0f032421b6fdef13e2a11a12fc6d7a9d63b54 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 24 Apr 2017 14:19:06 -0700 Subject: [PATCH 19/76] Restore master version of computeHipsOffset() and special case for HeadHMD target type --- .../animation/src/AnimInverseKinematics.cpp | 60 +++++++------------ 1 file changed, 22 insertions(+), 38 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index a9898fb4d2..ce199704a0 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -107,7 +107,6 @@ void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std:: AnimPose defaultPose = _skeleton->getAbsolutePose(targetVar.jointIndex, underPoses); glm::quat rotation = animVars.lookupRigToGeometry(targetVar.rotationVar, defaultPose.rot()); glm::vec3 translation = animVars.lookupRigToGeometry(targetVar.positionVar, defaultPose.trans()); - AnimPose absPose(glm::vec3(1.0f), rotation, translation); target.setPose(rotation, translation); target.setIndex(targetVar.jointIndex); @@ -155,7 +154,7 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const std::vector MAX_ERROR_TOLERANCE && numLoops < MAX_IK_LOOPS) { ++numLoops; @@ -248,8 +247,6 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe // the tip's parent-relative as we proceed up the chain glm::quat tipParentOrientation = absolutePoses[pivotIndex].rot(); - // AJT: REMOVE - /* if (targetType == IKTarget::Type::HmdHead) { // rotate tip directly to target orientation tipOrientation = target.getRotation(); @@ -267,7 +264,6 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe // store the relative rotation change in the accumulator _accumulators[tipIndex].add(tipRelativeRotation, target.getWeight()); } - */ // cache tip absolute position glm::vec3 tipPosition = absolutePoses[tipIndex].trans(); @@ -544,13 +540,7 @@ void AnimInverseKinematics::computeHipsOffset(const std::vector& targe // measure new _hipsOffset for next frame // by looking for discrepancies between where a targeted endEffector is // and where it wants to be (after IK solutions are done) - - // OUTOFBODY_HACK:use weighted average between HMD and other targets - float HMD_WEIGHT = 10.0f; - float OTHER_WEIGHT = 1.0f; - float totalWeight = 0.0f; - - glm::vec3 additionalHipsOffset = Vectors::ZERO; + glm::vec3 newHipsOffset = Vectors::ZERO; for (auto& target: targets) { int targetIndex = target.getIndex(); if (targetIndex == _headIndex && _headIndex != -1) { @@ -561,46 +551,40 @@ void AnimInverseKinematics::computeHipsOffset(const std::vector& targe glm::vec3 under = _skeleton->getAbsolutePose(_headIndex, underPoses).trans(); glm::vec3 actual = _skeleton->getAbsolutePose(_headIndex, _relativePoses).trans(); const float HEAD_OFFSET_SLAVE_FACTOR = 0.65f; - additionalHipsOffset += (OTHER_WEIGHT * HEAD_OFFSET_SLAVE_FACTOR) * (under - actual); - totalWeight += OTHER_WEIGHT; + newHipsOffset += HEAD_OFFSET_SLAVE_FACTOR * (actual - under); } else if (target.getType() == IKTarget::Type::HmdHead) { + // we want to shift the hips to bring the head to its designated position glm::vec3 actual = _skeleton->getAbsolutePose(_headIndex, _relativePoses).trans(); - glm::vec3 thisOffset = target.getTranslation() - actual; - glm::vec3 futureHipsOffset = _hipsOffset + thisOffset; - if (glm::length(glm::vec2(futureHipsOffset.x, futureHipsOffset.z)) < _maxHipsOffsetLength) { - // it is imperative to shift the hips and bring the head to its designated position - // so we slam newHipsOffset here and ignore all other targets - additionalHipsOffset = futureHipsOffset - _hipsOffset; - totalWeight = 0.0f; - break; - } else { - additionalHipsOffset += HMD_WEIGHT * (target.getTranslation() - actual); - totalWeight += HMD_WEIGHT; - } + _hipsOffset += target.getTranslation() - actual; + // and ignore all other targets + newHipsOffset = _hipsOffset; + break; + } else if (target.getType() == IKTarget::Type::RotationAndPosition) { + glm::vec3 actualPosition = _skeleton->getAbsolutePose(targetIndex, _relativePoses).trans(); + glm::vec3 targetPosition = target.getTranslation(); + newHipsOffset += targetPosition - actualPosition; + + // Add downward pressure on the hips + newHipsOffset *= 0.95f; + newHipsOffset -= 1.0f; } } else if (target.getType() == IKTarget::Type::RotationAndPosition) { glm::vec3 actualPosition = _skeleton->getAbsolutePose(targetIndex, _relativePoses).trans(); glm::vec3 targetPosition = target.getTranslation(); - additionalHipsOffset += OTHER_WEIGHT * (targetPosition - actualPosition); - totalWeight += OTHER_WEIGHT; + newHipsOffset += targetPosition - actualPosition; } } - if (totalWeight > 1.0f) { - additionalHipsOffset /= totalWeight; - } // smooth transitions by relaxing _hipsOffset toward the new value const float HIPS_OFFSET_SLAVE_TIMESCALE = 0.10f; float tau = dt < HIPS_OFFSET_SLAVE_TIMESCALE ? dt / HIPS_OFFSET_SLAVE_TIMESCALE : 1.0f; - _hipsOffset += additionalHipsOffset * tau; + _hipsOffset += (newHipsOffset - _hipsOffset) * tau; - // clamp the horizontal component of the hips offset - float hipsOffsetLength2D = glm::length(glm::vec2(_hipsOffset.x, _hipsOffset.z)); - if (hipsOffsetLength2D > _maxHipsOffsetLength) { - _hipsOffset.x *= _maxHipsOffsetLength / hipsOffsetLength2D; - _hipsOffset.z *= _maxHipsOffsetLength / hipsOffsetLength2D; + // clamp the hips offset + float hipsOffsetLength = glm::length(_hipsOffset); + if (hipsOffsetLength > _maxHipsOffsetLength) { + _hipsOffset *= _maxHipsOffsetLength / hipsOffsetLength; } - } void AnimInverseKinematics::setMaxHipsOffsetLength(float maxLength) { From deca26e9eba3d405458742a2394bc9a7a1d5cbf0 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 24 Apr 2017 14:33:14 -0700 Subject: [PATCH 20/76] Fix for HMDHead tip constraint --- libraries/animation/src/AnimInverseKinematics.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index ce199704a0..9f6a9ed56f 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -248,17 +248,18 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe glm::quat tipParentOrientation = absolutePoses[pivotIndex].rot(); if (targetType == IKTarget::Type::HmdHead) { + // rotate tip directly to target orientation tipOrientation = target.getRotation(); - glm::quat tipRelativeRotation = glm::normalize(tipOrientation * glm::inverse(tipParentOrientation)); + glm::quat tipRelativeRotation = glm::inverse(tipParentOrientation) * tipOrientation; - // enforce tip's constraint + // then enforce tip's constraint RotationConstraint* constraint = getConstraint(tipIndex); if (constraint) { bool constrained = constraint->apply(tipRelativeRotation); if (constrained) { - tipOrientation = glm::normalize(tipRelativeRotation * tipParentOrientation); - tipRelativeRotation = glm::normalize(tipOrientation * glm::inverse(tipParentOrientation)); + tipOrientation = tipParentOrientation * tipRelativeRotation; + tipRelativeRotation = tipRelativeRotation; } } // store the relative rotation change in the accumulator From 8adbe34c2736e20e2dc77c335af27b5f3fdf97a7 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 24 Apr 2017 14:36:56 -0700 Subject: [PATCH 21/76] added comment --- libraries/animation/src/AnimInverseKinematics.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 9f6a9ed56f..05a5d3bac6 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -247,6 +247,8 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe // the tip's parent-relative as we proceed up the chain glm::quat tipParentOrientation = absolutePoses[pivotIndex].rot(); + // NOTE: if this code is removed, the head will remain rigid, causing the spine/hips to thrust forward backward + // as the head is nodded. if (targetType == IKTarget::Type::HmdHead) { // rotate tip directly to target orientation From 8b16a7c7e19ba4496a3136db154286244b8ddee6 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 24 Apr 2017 16:40:24 -0700 Subject: [PATCH 22/76] Added hipsIkTest.js test script --- scripts/developer/tests/hipsIkTest.js | 118 ++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 scripts/developer/tests/hipsIkTest.js diff --git a/scripts/developer/tests/hipsIkTest.js b/scripts/developer/tests/hipsIkTest.js new file mode 100644 index 0000000000..340d1ae7a0 --- /dev/null +++ b/scripts/developer/tests/hipsIkTest.js @@ -0,0 +1,118 @@ +// +// hipsIKTest.js +// +// Created by Anthony Thibault on 4/24/17 +// Copyright 2017 High Fidelity, Inc. +// +// Test procedural manipulation of the Avatar hips IK target. +// Pull the left and right triggers on your hand controllers, you hips should begin to gyrate in an amusing mannor. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +/* global Xform */ +Script.include("/~/system/libraries/Xform.js"); + +var calibrated = false; +var rightTriggerPressed = false; +var leftTriggerPressed = false; + +var MAPPING_NAME = "com.highfidelity.hipsIkTest"; + +var mapping = Controller.newMapping(MAPPING_NAME); +mapping.from([Controller.Standard.RTClick]).peek().to(function (value) { + rightTriggerPressed = (value !== 0) ? true : false; +}); +mapping.from([Controller.Standard.LTClick]).peek().to(function (value) { + leftTriggerPressed = (value !== 0) ? true : false; +}); + +Controller.enableMapping(MAPPING_NAME); + +var ANIM_VARS = [ + "headType", + "hipsType", + "hipsPosition", + "hipsRotation" +]; + +var ZERO = {x: 0, y: 0, z: 0}; +var X_AXIS = {x: 1, y: 0, z: 0}; +var Y_AXIS = {x: 0, y: 1, z: 0}; +var Y_180 = {x: 0, y: 1, z: 0, w: 0}; +var Y_180_XFORM = new Xform(Y_180, {x: 0, y: 0, z: 0}); + +var hips = undefined; + +function computeCurrentXform(jointIndex) { + var currentXform = new Xform(MyAvatar.getAbsoluteJointRotationInObjectFrame(jointIndex), + MyAvatar.getAbsoluteJointTranslationInObjectFrame(jointIndex)); + return Xform.mul(Y_180_XFORM, currentXform); +} + +function calibrate() { + hips = computeCurrentXform(MyAvatar.getJointIndex("Hips")); +} + +var ikTypes = { + RotationAndPosition: 0, + RotationOnly: 1, + HmdHead: 2, + HipsRelativeRotationAndPosition: 3, + Off: 4 +}; + +function circleOffset(radius, theta, normal) { + var pos = {x: radius * Math.cos(theta), y: radius * Math.sin(theta), z: 0}; + var lookAtRot = Quat.lookAt(normal, ZERO, X_AXIS); + return Vec3.multiplyQbyV(lookAtRot, pos); +} + +var handlerId; + +function update(dt) { + if (rightTriggerPressed && leftTriggerPressed) { + if (!calibrated) { + calibrate(); + calibrated = true; + + if (handlerId) { + MyAvatar.removeAnimationStateHandler(handlerId); + handlerId = undefined; + } else { + + var n = Y_AXIS; + var t = 0; + handlerId = MyAvatar.addAnimationStateHandler(function (props) { + + t += (1 / 60) * 4; + var result = {}, xform; + if (hips) { + xform = hips; + result.hipsType = ikTypes.RotationAndPosition; + result.hipsPosition = Vec3.sum(xform.pos, circleOffset(0.1, t, n)); + result.hipsRotation = xform.rot; + result.headType = ikTypes.RotationAndPosition; + } else { + result.headType = ikTypes.HmdHead; + result.hipsType = props.hipsType; + result.hipsPosition = props.hipsPosition; + result.hipsRotation = props.hipsRotation; + } + + return result; + }, ANIM_VARS); + } + } + } else { + calibrated = false; + } +} + +Script.update.connect(update); + +Script.scriptEnding.connect(function () { + Controller.disableMapping(MAPPING_NAME); + Script.update.disconnect(update); +}); + From 937f308ba86bcc63e07f0bf30a62089553cd4a6a Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 24 Apr 2017 17:04:16 -0700 Subject: [PATCH 23/76] code cleanup --- interface/src/avatar/MyAvatar.h | 9 ++++----- .../animation/src/AnimInverseKinematics.cpp | 16 +--------------- 2 files changed, 5 insertions(+), 20 deletions(-) diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index b5bea23aa3..0146bd11a4 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -465,6 +465,10 @@ public: void removeHoldAction(AvatarActionHold* holdAction); // thread-safe void updateHoldActions(const AnimPose& prePhysicsPose, const AnimPose& postUpdatePose); + // derive avatar body position and orientation from the current HMD Sensor location. + // results are in HMD frame + glm::mat4 deriveBodyFromHMDSensor() const; + public slots: void increaseSize(); void decreaseSize(); @@ -553,11 +557,6 @@ private: void setVisibleInSceneIfReady(Model* model, render::ScenePointer scene, bool visiblity); - // AJT: FIX ME... reorder this -public: - // derive avatar body position and orientation from the current HMD Sensor location. - // results are in HMD frame - glm::mat4 deriveBodyFromHMDSensor() const; private: virtual void updatePalms() override {} diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 05a5d3bac6..83419afe65 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -828,14 +828,7 @@ void AnimInverseKinematics::initConstraints() { const float MAX_SPINE_TWIST = PI / 20.0f; stConstraint->setTwistLimits(-MAX_SPINE_TWIST, MAX_SPINE_TWIST); - /* - std::vector minDots; - const float MAX_SPINE_SWING = PI / 10.0f; - minDots.push_back(cosf(MAX_SPINE_SWING)); - stConstraint->setSwingLimits(minDots); - */ - - // AJT: limit lateral swings more then forward-backward swings + // limit lateral swings more then forward-backward swings setEllipticalSwingLimits(stConstraint, PI / 30.0f, PI / 20.0f); if (0 == baseName.compare("Spine1", Qt::CaseSensitive) @@ -851,13 +844,6 @@ void AnimInverseKinematics::initConstraints() { const float MAX_NECK_TWIST = PI / 10.0f; stConstraint->setTwistLimits(-MAX_NECK_TWIST, MAX_NECK_TWIST); - /* - std::vector minDots; - const float MAX_NECK_SWING = PI / 8.0f; - minDots.push_back(cosf(MAX_NECK_SWING)); - stConstraint->setSwingLimits(minDots); - */ - setEllipticalSwingLimits(stConstraint, PI / 10.0f, PI / 8.0f); constraint = static_cast(stConstraint); From 4336e22f5a56752c2c2d954cb280f2abf6ee76ae Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 24 Apr 2017 17:06:02 -0700 Subject: [PATCH 24/76] clang warning fix --- libraries/animation/src/AnimInverseKinematics.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 83419afe65..b36abebb1b 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -458,7 +458,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars { PROFILE_RANGE_EX(simulation_animation, "ik/shiftHips", 0xffff00ff, 0); - if (_hipsTargetIndex >= 0 && _hipsTargetIndex < targets.size()) { + if (_hipsTargetIndex >= 0 && _hipsTargetIndex < (int)targets.size()) { // slam the hips to match the _hipsTarget AnimPose absPose = targets[_hipsTargetIndex].getPose(); int parentIndex = _skeleton->getParentIndex(targets[_hipsTargetIndex].getIndex()); From ee9b953b1c8cc3bce5c9cea72e33aa8c46a90c4f Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Tue, 25 Apr 2017 12:12:26 -0700 Subject: [PATCH 25/76] typo --- scripts/system/tablet-goto.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/tablet-goto.js b/scripts/system/tablet-goto.js index bf4f79a346..8235b69c90 100644 --- a/scripts/system/tablet-goto.js +++ b/scripts/system/tablet-goto.js @@ -138,7 +138,7 @@ return; } stories[story.id] = story; - var message = story.username + " says something is happending in " + story.place_name + ". Open GOTO to join them."; + var message = story.username + " says something is happening in " + story.place_name + ". Open GOTO to join them."; Window.displayAnnouncement(message); didNotify = true; }); From ce8b71ff9464669ad324ee814ee8c93f447da02b Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 25 Apr 2017 15:05:52 -0700 Subject: [PATCH 26/76] viveMotionCapture.js: can now disable puck control, with a second controller squeeze --- scripts/developer/tests/viveMotionCapture.js | 130 +++++++++---------- 1 file changed, 65 insertions(+), 65 deletions(-) diff --git a/scripts/developer/tests/viveMotionCapture.js b/scripts/developer/tests/viveMotionCapture.js index 6cb0f92b9b..5496b475be 100644 --- a/scripts/developer/tests/viveMotionCapture.js +++ b/scripts/developer/tests/viveMotionCapture.js @@ -8,7 +8,7 @@ var TRACKED_OBJECT_POSES = [ "TrackedObject12", "TrackedObject13", "TrackedObject14", "TrackedObject15" ]; -var calibrated = false; +var triggerPressHandled = false; var rightTriggerPressed = false; var leftTriggerPressed = false; @@ -43,21 +43,6 @@ var SENSOR_CONFIG_NAMES = [ "Auto" ]; -var ANIM_VARS = [ - "leftFootType", - "leftFootPosition", - "leftFootRotation", - "rightFootType", - "rightFootPosition", - "rightFootRotation", - "hipsType", - "hipsPosition", - "hipsRotation", - "spine2Type", - "spine2Position", - "spine2Rotation" -]; - var sensorConfig = AUTO; var Y_180 = {x: 0, y: 1, z: 0, w: 0}; @@ -86,7 +71,7 @@ function computeDefaultToReferenceXform() { return defaultToReferenceXform; } else { - return new Xform.ident(); + return Xform.ident(); } } @@ -200,71 +185,86 @@ function computeIKTargetXform(jointInfo) { function update(dt) { if (rightTriggerPressed && leftTriggerPressed) { - if (!calibrated) { - calibrate(); - calibrated = true; - + if (!triggerPressHandled) { + triggerPressHandled = true; if (handlerId) { - MyAvatar.removeAnimationStateHandler(handlerId); - } + print("AJT: UN-CALIBRATE!"); - handlerId = MyAvatar.addAnimationStateHandler(function (props) { - - var result = {}, xform; - if (rightFoot) { - xform = computeIKTargetXform(rightFoot); - result.rightFootType = ikTypes.RotationAndPosition; - result.rightFootPosition = xform.pos; - result.rightFootRotation = xform.rot; - } else { - result.rightFootType = props.rightFootType; - result.rightFootPosition = props.rightFootPosition; - result.rightFootRotation = props.rightFootRotation; + // go back to normal, vive pucks will be ignored. + leftFoot = undefined; + rightFoot = undefined; + hips = undefined; + spine2 = undefined; + if (handlerId) { + print("AJT: un-hooking animation state handler"); + MyAvatar.removeAnimationStateHandler(handlerId); + handlerId = undefined; } + } else { + print("AJT: CALIBRATE!"); + calibrate(); + + var animVars = []; if (leftFoot) { - xform = computeIKTargetXform(leftFoot); - result.leftFootType = ikTypes.RotationAndPosition; - result.leftFootPosition = xform.pos; - result.leftFootRotation = xform.rot; - } else { - result.leftFootType = props.leftFootType; - result.leftFootPosition = props.leftFootPosition; - result.leftFootRotation = props.leftFootRotation; + animVars.push("leftFootType"); + animVars.push("leftFootPosition"); + animVars.push("leftFootRotation"); + } + if (rightFoot) { + animVars.push("rightFootType"); + animVars.push("rightFootPosition"); + animVars.push("rightFootRotation"); } - if (hips) { - xform = computeIKTargetXform(hips); - result.hipsType = ikTypes.RotationAndPosition; - result.hipsPosition = xform.pos; - result.hipsRotation = xform.rot; - } else { - result.hipsType = props.hipsType; - result.hipsPosition = props.hipsPosition; - result.hipsRotation = props.hipsRotation; + animVars.push("hipsType"); + animVars.push("hipsPosition"); + animVars.push("hipsRotation"); } - if (spine2) { - xform = computeIKTargetXform(spine2); - result.spine2Type = ikTypes.RotationAndPosition; - result.spine2Position = xform.pos; - result.spine2Rotation = xform.rot; - } else { - result.spine2Type = ikTypes.Off; + animVars.push("spine2Type"); + animVars.push("spine2Position"); + animVars.push("spine2Rotation"); } - return result; - }, ANIM_VARS); - + // hook up new anim state handler that maps vive pucks to ik system. + handlerId = MyAvatar.addAnimationStateHandler(function (props) { + var result = {}, xform; + if (rightFoot) { + xform = computeIKTargetXform(rightFoot); + result.rightFootType = ikTypes.RotationAndPosition; + result.rightFootPosition = xform.pos; + result.rightFootRotation = xform.rot; + } + if (leftFoot) { + xform = computeIKTargetXform(leftFoot); + result.leftFootType = ikTypes.RotationAndPosition; + result.leftFootPosition = xform.pos; + result.leftFootRotation = xform.rot; + } + if (hips) { + xform = computeIKTargetXform(hips); + result.hipsType = ikTypes.RotationAndPosition; + result.hipsPosition = xform.pos; + result.hipsRotation = xform.rot; + } + if (spine2) { + xform = computeIKTargetXform(spine2); + result.spine2Type = ikTypes.RotationAndPosition; + result.spine2Position = xform.pos; + result.spine2Rotation = xform.rot; + } + return result; + }, animVars); + } } } else { - calibrated = false; + triggerPressHandled = false; } var drawMarkers = false; if (drawMarkers) { var RED = {x: 1, y: 0, z: 0, w: 1}; - var GREEN = {x: 0, y: 1, z: 0, w: 1}; var BLUE = {x: 0, y: 0, z: 1, w: 1}; if (leftFoot) { @@ -304,4 +304,4 @@ Script.scriptEnding.connect(function () { Controller.disableMapping(MAPPING_NAME); Script.update.disconnect(update); }); -var TRIGGER_OFF_VALUE = 0.1; + From 0f8ee7051cf0ebd22bc036af2732dc0c017ac17e Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Wed, 26 Apr 2017 08:20:12 -0700 Subject: [PATCH 27/76] lint --- scripts/system/notifications.js | 1083 ++++++++++++++++--------------- scripts/system/tablet-goto.js | 56 +- 2 files changed, 571 insertions(+), 568 deletions(-) diff --git a/scripts/system/notifications.js b/scripts/system/notifications.js index c08cb44c0c..6429d6e0c6 100644 --- a/scripts/system/notifications.js +++ b/scripts/system/notifications.js @@ -1,5 +1,6 @@ "use strict"; - +/*jslint vars:true, plusplus:true, forin:true*/ +/*global Script, Settings, Window, Controller, Overlays, SoundArray, LODManager, MyAvatar, Tablet, Camera, HMD, Menu, Quat, Vec3*/ // // notifications.js // Version 0.801 @@ -56,614 +57,614 @@ // } // } -/* global Script, Controller, Overlays, SoundArray, Quat, Vec3, MyAvatar, Menu, HMD, AudioDevice, LODManager, Settings, Camera */ -(function() { // BEGIN LOCAL_SCOPE +(function () { // BEGIN LOCAL_SCOPE -Script.include("./libraries/soundArray.js"); + Script.include("./libraries/soundArray.js"); -var width = 340.0; //width of notification overlay -var windowDimensions = Controller.getViewportDimensions(); // get the size of the interface window -var overlayLocationX = (windowDimensions.x - (width + 20.0)); // positions window 20px from the right of the interface window -var buttonLocationX = overlayLocationX + (width - 28.0); -var locationY = 20.0; // position down from top of interface window -var topMargin = 13.0; -var leftMargin = 10.0; -var textColor = { red: 228, green: 228, blue: 228}; // text color -var backColor = { red: 2, green: 2, blue: 2}; // background color was 38,38,38 -var backgroundAlpha = 0; -var fontSize = 12.0; -var PERSIST_TIME_2D = 10.0; // Time in seconds before notification fades -var PERSIST_TIME_3D = 15.0; -var persistTime = PERSIST_TIME_2D; -var frame = 0; -var ourWidth = Window.innerWidth; -var ourHeight = Window.innerHeight; -var ctrlIsPressed = false; -var ready = true; -var MENU_NAME = 'Tools > Notifications'; -var PLAY_NOTIFICATION_SOUNDS_MENU_ITEM = "Play Notification Sounds"; -var NOTIFICATION_MENU_ITEM_POST = " Notifications"; -var PLAY_NOTIFICATION_SOUNDS_SETTING = "play_notification_sounds"; -var PLAY_NOTIFICATION_SOUNDS_TYPE_SETTING_PRE = "play_notification_sounds_type_"; -var lodTextID = false; + var width = 340.0; //width of notification overlay + var windowDimensions = Controller.getViewportDimensions(); // get the size of the interface window + var overlayLocationX = (windowDimensions.x - (width + 20.0)); // positions window 20px from the right of the interface window + var buttonLocationX = overlayLocationX + (width - 28.0); + var locationY = 20.0; // position down from top of interface window + var topMargin = 13.0; + var leftMargin = 10.0; + var textColor = { red: 228, green: 228, blue: 228}; // text color + var backColor = { red: 2, green: 2, blue: 2}; // background color was 38,38,38 + var backgroundAlpha = 0; + var fontSize = 12.0; + var PERSIST_TIME_2D = 10.0; // Time in seconds before notification fades + var PERSIST_TIME_3D = 15.0; + var persistTime = PERSIST_TIME_2D; + var frame = 0; + var ctrlIsPressed = false; + var ready = true; + var MENU_NAME = 'Tools > Notifications'; + var PLAY_NOTIFICATION_SOUNDS_MENU_ITEM = "Play Notification Sounds"; + var NOTIFICATION_MENU_ITEM_POST = " Notifications"; + var PLAY_NOTIFICATION_SOUNDS_SETTING = "play_notification_sounds"; + var PLAY_NOTIFICATION_SOUNDS_TYPE_SETTING_PRE = "play_notification_sounds_type_"; + var lodTextID = false; -var NotificationType = { - UNKNOWN: 0, - SNAPSHOT: 1, - LOD_WARNING: 2, - CONNECTION_REFUSED: 3, - EDIT_ERROR: 4, - TABLET: 5, - CONNECTION: 6, - properties: [ - { text: "Snapshot" }, - { text: "Level of Detail" }, - { text: "Connection Refused" }, - { text: "Edit error" }, - { text: "Tablet" }, - { text: "Connection" } - ], - getTypeFromMenuItem: function(menuItemName) { - if (menuItemName.substr(menuItemName.length - NOTIFICATION_MENU_ITEM_POST.length) !== NOTIFICATION_MENU_ITEM_POST) { - return NotificationType.UNKNOWN; - } - var preMenuItemName = menuItemName.substr(0, menuItemName.length - NOTIFICATION_MENU_ITEM_POST.length); - for (var type in this.properties) { - if (this.properties[type].text === preMenuItemName) { - return parseInt(type) + 1; + var NotificationType = { + UNKNOWN: 0, + SNAPSHOT: 1, + LOD_WARNING: 2, + CONNECTION_REFUSED: 3, + EDIT_ERROR: 4, + TABLET: 5, + CONNECTION: 6, + properties: [ + { text: "Snapshot" }, + { text: "Level of Detail" }, + { text: "Connection Refused" }, + { text: "Edit error" }, + { text: "Tablet" }, + { text: "Connection" } + ], + getTypeFromMenuItem: function (menuItemName) { + var type; + if (menuItemName.substr(menuItemName.length - NOTIFICATION_MENU_ITEM_POST.length) !== NOTIFICATION_MENU_ITEM_POST) { + return NotificationType.UNKNOWN; } + var preMenuItemName = menuItemName.substr(0, menuItemName.length - NOTIFICATION_MENU_ITEM_POST.length); + for (type in this.properties) { + if (this.properties[type].text === preMenuItemName) { + return parseInt(type, 10) + 1; + } + } + return NotificationType.UNKNOWN; + }, + getMenuString: function (type) { + return this.properties[type - 1].text + NOTIFICATION_MENU_ITEM_POST; } - return NotificationType.UNKNOWN; - }, - getMenuString: function(type) { - return this.properties[type - 1].text + NOTIFICATION_MENU_ITEM_POST; - } -}; - -var randomSounds = new SoundArray({ localOnly: true }, true); -var numberOfSounds = 2; -for (var i = 1; i <= numberOfSounds; i++) { - randomSounds.addSound(Script.resolvePath("assets/sounds/notification-general"+ i + ".raw")); -} - -var notifications = []; -var buttons = []; -var times = []; -var heights = []; -var myAlpha = []; -var arrays = []; -var isOnHMD = false, - NOTIFICATIONS_3D_DIRECTION = 0.0, // Degrees from avatar orientation. - NOTIFICATIONS_3D_DISTANCE = 0.6, // Horizontal distance from avatar position. - NOTIFICATIONS_3D_ELEVATION = -0.8, // Height of top middle of top notification relative to avatar eyes. - NOTIFICATIONS_3D_YAW = 0.0, // Degrees relative to notifications direction. - NOTIFICATIONS_3D_PITCH = -60.0, // Degrees from vertical. - NOTIFICATION_3D_SCALE = 0.002, // Multiplier that converts 2D overlay dimensions to 3D overlay dimensions. - NOTIFICATION_3D_BUTTON_WIDTH = 40 * NOTIFICATION_3D_SCALE, // Need a little more room for button in 3D. - overlay3DDetails = []; - -// push data from above to the 2 dimensional array -function createArrays(notice, button, createTime, height, myAlpha) { - arrays.push([notice, button, createTime, height, myAlpha]); -} - -// This handles the final dismissal of a notification after fading -function dismiss(firstNoteOut, firstButOut, firstOut) { - if (firstNoteOut == lodTextID) { - lodTextID = false; - } - - Overlays.deleteOverlay(firstNoteOut); - Overlays.deleteOverlay(firstButOut); - notifications.splice(firstOut, 1); - buttons.splice(firstOut, 1); - times.splice(firstOut, 1); - heights.splice(firstOut, 1); - myAlpha.splice(firstOut, 1); - overlay3DDetails.splice(firstOut, 1); -} - -function fadeIn(noticeIn, buttonIn) { - var q = 0, - qFade, - pauseTimer = null; - - pauseTimer = Script.setInterval(function () { - q += 1; - qFade = q / 10.0; - Overlays.editOverlay(noticeIn, { alpha: qFade }); - Overlays.editOverlay(buttonIn, { alpha: qFade }); - if (q >= 9.0) { - Script.clearInterval(pauseTimer); - } - }, 10); -} - -// this fades the notification ready for dismissal, and removes it from the arrays -function fadeOut(noticeOut, buttonOut, arraysOut) { - var r = 9.0, - rFade, - pauseTimer = null; - - pauseTimer = Script.setInterval(function () { - r -= 1; - rFade = Math.max(0.0, r / 10.0); - Overlays.editOverlay(noticeOut, { alpha: rFade }); - Overlays.editOverlay(buttonOut, { alpha: rFade }); - if (r <= 0) { - dismiss(noticeOut, buttonOut, arraysOut); - arrays.splice(arraysOut, 1); - ready = true; - Script.clearInterval(pauseTimer); - } - }, 20); -} - -function calculate3DOverlayPositions(noticeWidth, noticeHeight, y) { - // Calculates overlay positions and orientations in avatar coordinates. - var noticeY, - originOffset, - notificationOrientation, - notificationPosition, - buttonPosition; - - // Notification plane positions - noticeY = -y * NOTIFICATION_3D_SCALE - noticeHeight / 2; - notificationPosition = { x: 0, y: noticeY, z: 0 }; - buttonPosition = { x: (noticeWidth - NOTIFICATION_3D_BUTTON_WIDTH) / 2, y: noticeY, z: 0.001 }; - - // Rotate plane - notificationOrientation = Quat.fromPitchYawRollDegrees(NOTIFICATIONS_3D_PITCH, - NOTIFICATIONS_3D_DIRECTION + NOTIFICATIONS_3D_YAW, 0); - notificationPosition = Vec3.multiplyQbyV(notificationOrientation, notificationPosition); - buttonPosition = Vec3.multiplyQbyV(notificationOrientation, buttonPosition); - - // Translate plane - originOffset = Vec3.multiplyQbyV(Quat.fromPitchYawRollDegrees(0, NOTIFICATIONS_3D_DIRECTION, 0), - { x: 0, y: 0, z: -NOTIFICATIONS_3D_DISTANCE }); - originOffset.y += NOTIFICATIONS_3D_ELEVATION; - notificationPosition = Vec3.sum(originOffset, notificationPosition); - buttonPosition = Vec3.sum(originOffset, buttonPosition); - - return { - notificationOrientation: notificationOrientation, - notificationPosition: notificationPosition, - buttonPosition: buttonPosition }; -} -// Pushes data to each array and sets up data for 2nd dimension array -// to handle auxiliary data not carried by the overlay class -// specifically notification "heights", "times" of creation, and . -function notify(notice, button, height, imageProperties, image) { - var notificationText, - noticeWidth, - noticeHeight, - positions, - last; + var randomSounds = new SoundArray({ localOnly: true }, true); + var numberOfSounds = 2; + var soundIndex; + for (soundIndex = 1; soundIndex <= numberOfSounds; soundIndex++) { + randomSounds.addSound(Script.resolvePath("assets/sounds/notification-general" + soundIndex + ".raw")); + } - if (isOnHMD) { - // Calculate 3D values from 2D overlay properties. + var notifications = []; + var buttons = []; + var times = []; + var heights = []; + var myAlpha = []; + var arrays = []; + var isOnHMD = false, + NOTIFICATIONS_3D_DIRECTION = 0.0, // Degrees from avatar orientation. + NOTIFICATIONS_3D_DISTANCE = 0.6, // Horizontal distance from avatar position. + NOTIFICATIONS_3D_ELEVATION = -0.8, // Height of top middle of top notification relative to avatar eyes. + NOTIFICATIONS_3D_YAW = 0.0, // Degrees relative to notifications direction. + NOTIFICATIONS_3D_PITCH = -60.0, // Degrees from vertical. + NOTIFICATION_3D_SCALE = 0.002, // Multiplier that converts 2D overlay dimensions to 3D overlay dimensions. + NOTIFICATION_3D_BUTTON_WIDTH = 40 * NOTIFICATION_3D_SCALE, // Need a little more room for button in 3D. + overlay3DDetails = []; - noticeWidth = notice.width * NOTIFICATION_3D_SCALE + NOTIFICATION_3D_BUTTON_WIDTH; - noticeHeight = notice.height * NOTIFICATION_3D_SCALE; + // push data from above to the 2 dimensional array + function createArrays(notice, button, createTime, height, myAlpha) { + arrays.push([notice, button, createTime, height, myAlpha]); + } - notice.size = { x: noticeWidth, y: noticeHeight }; - - positions = calculate3DOverlayPositions(noticeWidth, noticeHeight, notice.y); - - notice.parentID = MyAvatar.sessionUUID; - notice.parentJointIndex = -2; - - if (!image) { - notice.topMargin = 0.75 * notice.topMargin * NOTIFICATION_3D_SCALE; - notice.leftMargin = 2 * notice.leftMargin * NOTIFICATION_3D_SCALE; - notice.bottomMargin = 0; - notice.rightMargin = 0; - notice.lineHeight = 10.0 * (fontSize / 12.0) * NOTIFICATION_3D_SCALE; - notice.isFacingAvatar = false; - - notificationText = Overlays.addOverlay("text3d", notice); - notifications.push(notificationText); - } else { - notifications.push(Overlays.addOverlay("image3d", notice)); + // This handles the final dismissal of a notification after fading + function dismiss(firstNoteOut, firstButOut, firstOut) { + if (firstNoteOut === lodTextID) { + lodTextID = false; } - button.url = button.imageURL; - button.scale = button.width * NOTIFICATION_3D_SCALE; - button.isFacingAvatar = false; - button.parentID = MyAvatar.sessionUUID; - button.parentJointIndex = -2; + Overlays.deleteOverlay(firstNoteOut); + Overlays.deleteOverlay(firstButOut); + notifications.splice(firstOut, 1); + buttons.splice(firstOut, 1); + times.splice(firstOut, 1); + heights.splice(firstOut, 1); + myAlpha.splice(firstOut, 1); + overlay3DDetails.splice(firstOut, 1); + } - buttons.push((Overlays.addOverlay("image3d", button))); - overlay3DDetails.push({ - notificationOrientation: positions.notificationOrientation, - notificationPosition: positions.notificationPosition, - buttonPosition: positions.buttonPosition, - width: noticeWidth, - height: noticeHeight - }); + function fadeIn(noticeIn, buttonIn) { + var q = 0, + qFade, + pauseTimer = null; + pauseTimer = Script.setInterval(function () { + q += 1; + qFade = q / 10.0; + Overlays.editOverlay(noticeIn, { alpha: qFade }); + Overlays.editOverlay(buttonIn, { alpha: qFade }); + if (q >= 9.0) { + Script.clearInterval(pauseTimer); + } + }, 10); + } - var defaultEyePosition, - avatarOrientation, - notificationPosition, + // this fades the notification ready for dismissal, and removes it from the arrays + function fadeOut(noticeOut, buttonOut, arraysOut) { + var r = 9.0, + rFade, + pauseTimer = null; + + pauseTimer = Script.setInterval(function () { + r -= 1; + rFade = Math.max(0.0, r / 10.0); + Overlays.editOverlay(noticeOut, { alpha: rFade }); + Overlays.editOverlay(buttonOut, { alpha: rFade }); + if (r <= 0) { + dismiss(noticeOut, buttonOut, arraysOut); + arrays.splice(arraysOut, 1); + ready = true; + Script.clearInterval(pauseTimer); + } + }, 20); + } + + function calculate3DOverlayPositions(noticeWidth, noticeHeight, y) { + // Calculates overlay positions and orientations in avatar coordinates. + var noticeY, + originOffset, notificationOrientation, + notificationPosition, buttonPosition; - if (isOnHMD && notifications.length > 0) { - // Update 3D overlays to maintain positions relative to avatar - defaultEyePosition = MyAvatar.getDefaultEyePosition(); - avatarOrientation = MyAvatar.orientation; + // Notification plane positions + noticeY = -y * NOTIFICATION_3D_SCALE - noticeHeight / 2; + notificationPosition = { x: 0, y: noticeY, z: 0 }; + buttonPosition = { x: (noticeWidth - NOTIFICATION_3D_BUTTON_WIDTH) / 2, y: noticeY, z: 0.001 }; - for (i = 0; i < notifications.length; i += 1) { - notificationPosition = Vec3.sum(defaultEyePosition, - Vec3.multiplyQbyV(avatarOrientation, - overlay3DDetails[i].notificationPosition)); - notificationOrientation = Quat.multiply(avatarOrientation, - overlay3DDetails[i].notificationOrientation); - buttonPosition = Vec3.sum(defaultEyePosition, - Vec3.multiplyQbyV(avatarOrientation, - overlay3DDetails[i].buttonPosition)); - Overlays.editOverlay(notifications[i], { position: notificationPosition, - rotation: notificationOrientation }); - Overlays.editOverlay(buttons[i], { position: buttonPosition, rotation: notificationOrientation }); - } - } + // Rotate plane + notificationOrientation = Quat.fromPitchYawRollDegrees(NOTIFICATIONS_3D_PITCH, + NOTIFICATIONS_3D_DIRECTION + NOTIFICATIONS_3D_YAW, 0); + notificationPosition = Vec3.multiplyQbyV(notificationOrientation, notificationPosition); + buttonPosition = Vec3.multiplyQbyV(notificationOrientation, buttonPosition); - } else { - if (!image) { - notificationText = Overlays.addOverlay("text", notice); - notifications.push((notificationText)); - } else { - notifications.push(Overlays.addOverlay("image", notice)); - } - buttons.push(Overlays.addOverlay("image", button)); + // Translate plane + originOffset = Vec3.multiplyQbyV(Quat.fromPitchYawRollDegrees(0, NOTIFICATIONS_3D_DIRECTION, 0), + { x: 0, y: 0, z: -NOTIFICATIONS_3D_DISTANCE }); + originOffset.y += NOTIFICATIONS_3D_ELEVATION; + notificationPosition = Vec3.sum(originOffset, notificationPosition); + buttonPosition = Vec3.sum(originOffset, buttonPosition); + + return { + notificationOrientation: notificationOrientation, + notificationPosition: notificationPosition, + buttonPosition: buttonPosition + }; } - height = height + 1.0; - heights.push(height); - times.push(new Date().getTime() / 1000); - last = notifications.length - 1; - myAlpha.push(notifications[last].alpha); - createArrays(notifications[last], buttons[last], times[last], heights[last], myAlpha[last]); - fadeIn(notifications[last], buttons[last]); + // Pushes data to each array and sets up data for 2nd dimension array + // to handle auxiliary data not carried by the overlay class + // specifically notification "heights", "times" of creation, and . + function notify(notice, button, height, imageProperties, image) { + var notificationText, + noticeWidth, + noticeHeight, + positions, + last; - if (imageProperties && !image) { - var imageHeight = notice.width / imageProperties.aspectRatio; - notice = { - x: notice.x, - y: notice.y + height, - width: notice.width, - height: imageHeight, - subImage: { x: 0, y: 0 }, + if (isOnHMD) { + // Calculate 3D values from 2D overlay properties. + + noticeWidth = notice.width * NOTIFICATION_3D_SCALE + NOTIFICATION_3D_BUTTON_WIDTH; + noticeHeight = notice.height * NOTIFICATION_3D_SCALE; + + notice.size = { x: noticeWidth, y: noticeHeight }; + + positions = calculate3DOverlayPositions(noticeWidth, noticeHeight, notice.y); + + notice.parentID = MyAvatar.sessionUUID; + notice.parentJointIndex = -2; + + if (!image) { + notice.topMargin = 0.75 * notice.topMargin * NOTIFICATION_3D_SCALE; + notice.leftMargin = 2 * notice.leftMargin * NOTIFICATION_3D_SCALE; + notice.bottomMargin = 0; + notice.rightMargin = 0; + notice.lineHeight = 10.0 * (fontSize / 12.0) * NOTIFICATION_3D_SCALE; + notice.isFacingAvatar = false; + + notificationText = Overlays.addOverlay("text3d", notice); + notifications.push(notificationText); + } else { + notifications.push(Overlays.addOverlay("image3d", notice)); + } + + button.url = button.imageURL; + button.scale = button.width * NOTIFICATION_3D_SCALE; + button.isFacingAvatar = false; + button.parentID = MyAvatar.sessionUUID; + button.parentJointIndex = -2; + + buttons.push((Overlays.addOverlay("image3d", button))); + overlay3DDetails.push({ + notificationOrientation: positions.notificationOrientation, + notificationPosition: positions.notificationPosition, + buttonPosition: positions.buttonPosition, + width: noticeWidth, + height: noticeHeight + }); + + + var defaultEyePosition, + avatarOrientation, + notificationPosition, + notificationOrientation, + buttonPosition, + notificationIndex; + + if (isOnHMD && notifications.length > 0) { + // Update 3D overlays to maintain positions relative to avatar + defaultEyePosition = MyAvatar.getDefaultEyePosition(); + avatarOrientation = MyAvatar.orientation; + + for (notificationIndex = 0; notificationIndex < notifications.length; notificationIndex += 1) { + notificationPosition = Vec3.sum(defaultEyePosition, + Vec3.multiplyQbyV(avatarOrientation, + overlay3DDetails[notificationIndex].notificationPosition)); + notificationOrientation = Quat.multiply(avatarOrientation, + overlay3DDetails[notificationIndex].notificationOrientation); + buttonPosition = Vec3.sum(defaultEyePosition, + Vec3.multiplyQbyV(avatarOrientation, + overlay3DDetails[notificationIndex].buttonPosition)); + Overlays.editOverlay(notifications[notificationIndex], { position: notificationPosition, + rotation: notificationOrientation }); + Overlays.editOverlay(buttons[notificationIndex], { position: buttonPosition, rotation: notificationOrientation }); + } + } + + } else { + if (!image) { + notificationText = Overlays.addOverlay("text", notice); + notifications.push(notificationText); + } else { + notifications.push(Overlays.addOverlay("image", notice)); + } + buttons.push(Overlays.addOverlay("image", button)); + } + + height = height + 1.0; + heights.push(height); + times.push(new Date().getTime() / 1000); + last = notifications.length - 1; + myAlpha.push(notifications[last].alpha); + createArrays(notifications[last], buttons[last], times[last], heights[last], myAlpha[last]); + fadeIn(notifications[last], buttons[last]); + + if (imageProperties && !image) { + var imageHeight = notice.width / imageProperties.aspectRatio; + notice = { + x: notice.x, + y: notice.y + height, + width: notice.width, + height: imageHeight, + subImage: { x: 0, y: 0 }, + color: { red: 255, green: 255, blue: 255}, + visible: true, + imageURL: imageProperties.path, + alpha: backgroundAlpha + }; + notify(notice, button, imageHeight, imageProperties, true); + } + + return notificationText; + } + + var CLOSE_NOTIFICATION_ICON = Script.resolvePath("assets/images/close-small-light.svg"); + + // This function creates and sizes the overlays + function createNotification(text, notificationType, imageProperties) { + var count = (text.match(/\n/g) || []).length, + breakPoint = 43.0, // length when new line is added + extraLine = 0, + breaks = 0, + height = 40.0, + stack = 0, + level, + noticeProperties, + bLevel, + buttonProperties, + i; + + if (text.length >= breakPoint) { + breaks = count; + } + extraLine = breaks * 16.0; + for (i = 0; i < heights.length; i += 1) { + stack = stack + heights[i]; + } + + level = (stack + 20.0); + height = height + extraLine; + + noticeProperties = { + x: overlayLocationX, + y: level, + width: width, + height: height, + color: textColor, + backgroundColor: backColor, + alpha: backgroundAlpha, + topMargin: topMargin, + leftMargin: leftMargin, + font: {size: fontSize}, + text: text + }; + + bLevel = level + 12.0; + buttonProperties = { + x: buttonLocationX, + y: bLevel, + width: 10.0, + height: 10.0, + subImage: { x: 0, y: 0, width: 10, height: 10 }, + imageURL: CLOSE_NOTIFICATION_ICON, color: { red: 255, green: 255, blue: 255}, visible: true, - imageURL: imageProperties.path, alpha: backgroundAlpha }; - notify(notice, button, imageHeight, imageProperties, true); + + if (Menu.isOptionChecked(PLAY_NOTIFICATION_SOUNDS_MENU_ITEM) && + Menu.isOptionChecked(NotificationType.getMenuString(notificationType))) { + randomSounds.playRandom(); + } + + return notify(noticeProperties, buttonProperties, height, imageProperties); } - return notificationText; -} - -var CLOSE_NOTIFICATION_ICON = Script.resolvePath("assets/images/close-small-light.svg"); - -// This function creates and sizes the overlays -function createNotification(text, notificationType, imageProperties) { - var count = (text.match(/\n/g) || []).length, - breakPoint = 43.0, // length when new line is added - extraLine = 0, - breaks = 0, - height = 40.0, - stack = 0, - level, - noticeProperties, - bLevel, - buttonProperties, - i; - - if (text.length >= breakPoint) { - breaks = count; - } - extraLine = breaks * 16.0; - for (i = 0; i < heights.length; i += 1) { - stack = stack + heights[i]; + function deleteNotification(index) { + var notificationTextID = notifications[index]; + if (notificationTextID === lodTextID) { + lodTextID = false; + } + Overlays.deleteOverlay(notificationTextID); + Overlays.deleteOverlay(buttons[index]); + notifications.splice(index, 1); + buttons.splice(index, 1); + times.splice(index, 1); + heights.splice(index, 1); + myAlpha.splice(index, 1); + overlay3DDetails.splice(index, 1); + arrays.splice(index, 1); } - level = (stack + 20.0); - height = height + extraLine; - noticeProperties = { - x: overlayLocationX, - y: level, - width: width, - height: height, - color: textColor, - backgroundColor: backColor, - alpha: backgroundAlpha, - topMargin: topMargin, - leftMargin: leftMargin, - font: {size: fontSize}, - text: text - }; - - bLevel = level + 12.0; - buttonProperties = { - x: buttonLocationX, - y: bLevel, - width: 10.0, - height: 10.0, - subImage: { x: 0, y: 0, width: 10, height: 10 }, - imageURL: CLOSE_NOTIFICATION_ICON, - color: { red: 255, green: 255, blue: 255}, - visible: true, - alpha: backgroundAlpha - }; - - if (Menu.isOptionChecked(PLAY_NOTIFICATION_SOUNDS_MENU_ITEM) && - Menu.isOptionChecked(NotificationType.getMenuString(notificationType))) { - randomSounds.playRandom(); - } - - return notify(noticeProperties, buttonProperties, height, imageProperties); -} - -function deleteNotification(index) { - var notificationTextID = notifications[index]; - if (notificationTextID == lodTextID) { - lodTextID = false; - } - Overlays.deleteOverlay(notificationTextID); - Overlays.deleteOverlay(buttons[index]); - notifications.splice(index, 1); - buttons.splice(index, 1); - times.splice(index, 1); - heights.splice(index, 1); - myAlpha.splice(index, 1); - overlay3DDetails.splice(index, 1); - arrays.splice(index, 1); -} - - -// Trims extra whitespace and breaks into lines of length no more than MAX_LENGTH, breaking at spaces. Trims extra whitespace. -var MAX_LENGTH = 42; -function wordWrap(string) { - var finishedLines = [], currentLine = ''; - string.split(/\s/).forEach(function (word) { - var tail = currentLine ? ' ' + word : word; - if ((currentLine.length + tail.length) <= MAX_LENGTH) { - currentLine += tail; - } else { + // Trims extra whitespace and breaks into lines of length no more than MAX_LENGTH, breaking at spaces. Trims extra whitespace. + var MAX_LENGTH = 42; + function wordWrap(string) { + var finishedLines = [], currentLine = ''; + string.split(/\s/).forEach(function (word) { + var tail = currentLine ? ' ' + word : word; + if ((currentLine.length + tail.length) <= MAX_LENGTH) { + currentLine += tail; + } else { + finishedLines.push(currentLine); + currentLine = word; + } + }); + if (currentLine) { finishedLines.push(currentLine); - currentLine = word; } - }); - if (currentLine) { - finishedLines.push(currentLine); - } - return finishedLines.join('\n'); -} - -function update() { - var nextOverlay, - noticeOut, - buttonOut, - arraysOut, - positions, - i, - j, - k; - - if (isOnHMD !== HMD.active) { - while (arrays.length > 0) { - deleteNotification(0); - } - isOnHMD = !isOnHMD; - persistTime = isOnHMD ? PERSIST_TIME_3D : PERSIST_TIME_2D; - return; + return finishedLines.join('\n'); } - frame += 1; - if ((frame % 60.0) === 0) { // only update once a second - locationY = 20.0; - for (i = 0; i < arrays.length; i += 1) { //repositions overlays as others fade - nextOverlay = Overlays.getOverlayAtPoint({ x: overlayLocationX, y: locationY }); - Overlays.editOverlay(notifications[i], { x: overlayLocationX, y: locationY }); - Overlays.editOverlay(buttons[i], { x: buttonLocationX, y: locationY + 12.0 }); - if (isOnHMD) { - positions = calculate3DOverlayPositions(overlay3DDetails[i].width, - overlay3DDetails[i].height, locationY); - overlay3DDetails[i].notificationOrientation = positions.notificationOrientation; - overlay3DDetails[i].notificationPosition = positions.notificationPosition; - overlay3DDetails[i].buttonPosition = positions.buttonPosition; + function update() { + var noticeOut, + buttonOut, + arraysOut, + positions, + i, + j, + k; + + if (isOnHMD !== HMD.active) { + while (arrays.length > 0) { + deleteNotification(0); } - locationY = locationY + arrays[i][3]; + isOnHMD = !isOnHMD; + persistTime = isOnHMD ? PERSIST_TIME_3D : PERSIST_TIME_2D; + return; } - } - // This checks the age of the notification and prepares to fade it after 9.0 seconds (var persistTime - 1) - for (i = 0; i < arrays.length; i += 1) { - if (ready) { - j = arrays[i][2]; - k = j + persistTime; - if (k < (new Date().getTime() / 1000)) { - ready = false; - noticeOut = arrays[i][0]; - buttonOut = arrays[i][1]; - arraysOut = i; - fadeOut(noticeOut, buttonOut, arraysOut); + frame += 1; + if ((frame % 60.0) === 0) { // only update once a second + locationY = 20.0; + for (i = 0; i < arrays.length; i += 1) { //repositions overlays as others fade + Overlays.editOverlay(notifications[i], { x: overlayLocationX, y: locationY }); + Overlays.editOverlay(buttons[i], { x: buttonLocationX, y: locationY + 12.0 }); + if (isOnHMD) { + positions = calculate3DOverlayPositions(overlay3DDetails[i].width, + overlay3DDetails[i].height, locationY); + overlay3DDetails[i].notificationOrientation = positions.notificationOrientation; + overlay3DDetails[i].notificationPosition = positions.notificationPosition; + overlay3DDetails[i].buttonPosition = positions.buttonPosition; + } + locationY = locationY + arrays[i][3]; + } + } + + // This checks the age of the notification and prepares to fade it after 9.0 seconds (var persistTime - 1) + for (i = 0; i < arrays.length; i += 1) { + if (ready) { + j = arrays[i][2]; + k = j + persistTime; + if (k < (new Date().getTime() / 1000)) { + ready = false; + noticeOut = arrays[i][0]; + buttonOut = arrays[i][1]; + arraysOut = i; + fadeOut(noticeOut, buttonOut, arraysOut); + } } } } -} -var STARTUP_TIMEOUT = 500, // ms - startingUp = true, - startupTimer = null; + var STARTUP_TIMEOUT = 500, // ms + startingUp = true, + startupTimer = null; -function finishStartup() { - startingUp = false; - Script.clearTimeout(startupTimer); -} + function finishStartup() { + startingUp = false; + Script.clearTimeout(startupTimer); + } -function isStartingUp() { - // Is starting up until get no checks that it is starting up for STARTUP_TIMEOUT - if (startingUp) { - if (startupTimer) { - Script.clearTimeout(startupTimer); + function isStartingUp() { + // Is starting up until get no checks that it is starting up for STARTUP_TIMEOUT + if (startingUp) { + if (startupTimer) { + Script.clearTimeout(startupTimer); + } + startupTimer = Script.setTimeout(finishStartup, STARTUP_TIMEOUT); } - startupTimer = Script.setTimeout(finishStartup, STARTUP_TIMEOUT); - } - return startingUp; -} - -function onDomainConnectionRefused(reason) { - createNotification("Connection refused: " + reason, NotificationType.CONNECTION_REFUSED); -} - -function onEditError(msg) { - createNotification(wordWrap(msg), NotificationType.EDIT_ERROR); -} - -function onNotify(msg) { - createNotification(wordWrap(msg), NotificationType.UNKNOWN); // Needs a generic notification system for user feedback, thus using this -} - -function onSnapshotTaken(pathStillSnapshot, notify) { - if (notify) { - var imageProperties = { - path: "file:///" + pathStillSnapshot, - aspectRatio: Window.innerWidth / Window.innerHeight - }; - createNotification(wordWrap("Snapshot saved to " + pathStillSnapshot), NotificationType.SNAPSHOT, imageProperties); - } -} - -function tabletNotification() { - createNotification("Tablet needs your attention", NotificationType.TABLET); -} - -function processingGif() { - createNotification("Processing GIF snapshot...", NotificationType.SNAPSHOT); -} - -function connectionAdded(connectionName) { - createNotification(connectionName, NotificationType.CONNECTION); -} - -function connectionError(error) { - createNotification(wordWrap("Error trying to make connection: " + error), NotificationType.CONNECTION); -} - -// handles mouse clicks on buttons -function mousePressEvent(event) { - var pickRay, - clickedOverlay, - i; - - if (isOnHMD) { - pickRay = Camera.computePickRay(event.x, event.y); - clickedOverlay = Overlays.findRayIntersection(pickRay).overlayID; - } else { - clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y }); + return startingUp; } - for (i = 0; i < buttons.length; i += 1) { - if (clickedOverlay === buttons[i]) { - deleteNotification(i); + function onDomainConnectionRefused(reason) { + createNotification("Connection refused: " + reason, NotificationType.CONNECTION_REFUSED); + } + + function onEditError(msg) { + createNotification(wordWrap(msg), NotificationType.EDIT_ERROR); + } + + function onNotify(msg) { + createNotification(wordWrap(msg), NotificationType.UNKNOWN); // Needs a generic notification system for user feedback, thus using this + } + + function onSnapshotTaken(pathStillSnapshot, notify) { + if (notify) { + var imageProperties = { + path: "file:///" + pathStillSnapshot, + aspectRatio: Window.innerWidth / Window.innerHeight + }; + createNotification(wordWrap("Snapshot saved to " + pathStillSnapshot), NotificationType.SNAPSHOT, imageProperties); } } -} -// Control key remains active only while key is held down -function keyReleaseEvent(key) { - if (key.key === 16777249) { - ctrlIsPressed = false; + function tabletNotification() { + createNotification("Tablet needs your attention", NotificationType.TABLET); } -} -// Triggers notification on specific key driven events -function keyPressEvent(key) { - if (key.key === 16777249) { - ctrlIsPressed = true; + function processingGif() { + createNotification("Processing GIF snapshot...", NotificationType.SNAPSHOT); } -} -function setup() { - Menu.addMenu(MENU_NAME); - var checked = Settings.getValue(PLAY_NOTIFICATION_SOUNDS_SETTING); - checked = checked === '' ? true : checked; - Menu.addMenuItem({ - menuName: MENU_NAME, - menuItemName: PLAY_NOTIFICATION_SOUNDS_MENU_ITEM, - isCheckable: true, - isChecked: Settings.getValue(PLAY_NOTIFICATION_SOUNDS_SETTING) - }); - Menu.addSeparator(MENU_NAME, "Play sounds for:"); - for (var type in NotificationType.properties) { - checked = Settings.getValue(PLAY_NOTIFICATION_SOUNDS_TYPE_SETTING_PRE + (parseInt(type) + 1)); + function connectionAdded(connectionName) { + createNotification(connectionName, NotificationType.CONNECTION); + } + + function connectionError(error) { + createNotification(wordWrap("Error trying to make connection: " + error), NotificationType.CONNECTION); + } + + // handles mouse clicks on buttons + function mousePressEvent(event) { + var pickRay, + clickedOverlay, + i; + + if (isOnHMD) { + pickRay = Camera.computePickRay(event.x, event.y); + clickedOverlay = Overlays.findRayIntersection(pickRay).overlayID; + } else { + clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y }); + } + + for (i = 0; i < buttons.length; i += 1) { + if (clickedOverlay === buttons[i]) { + deleteNotification(i); + } + } + } + + // Control key remains active only while key is held down + function keyReleaseEvent(key) { + if (key.key === 16777249) { + ctrlIsPressed = false; + } + } + + // Triggers notification on specific key driven events + function keyPressEvent(key) { + if (key.key === 16777249) { + ctrlIsPressed = true; + } + } + + function setup() { + var type; + Menu.addMenu(MENU_NAME); + var checked = Settings.getValue(PLAY_NOTIFICATION_SOUNDS_SETTING); checked = checked === '' ? true : checked; Menu.addMenuItem({ menuName: MENU_NAME, - menuItemName: NotificationType.properties[type].text + NOTIFICATION_MENU_ITEM_POST, + menuItemName: PLAY_NOTIFICATION_SOUNDS_MENU_ITEM, isCheckable: true, - isChecked: checked + isChecked: Settings.getValue(PLAY_NOTIFICATION_SOUNDS_SETTING) }); + Menu.addSeparator(MENU_NAME, "Play sounds for:"); + for (type in NotificationType.properties) { + checked = Settings.getValue(PLAY_NOTIFICATION_SOUNDS_TYPE_SETTING_PRE + (parseInt(type, 10) + 1)); + checked = checked === '' ? true : checked; + Menu.addMenuItem({ + menuName: MENU_NAME, + menuItemName: NotificationType.properties[type].text + NOTIFICATION_MENU_ITEM_POST, + isCheckable: true, + isChecked: checked + }); + } } -} -// When our script shuts down, we should clean up all of our overlays -function scriptEnding() { - for (var i = 0; i < notifications.length; i++) { - Overlays.deleteOverlay(notifications[i]); - Overlays.deleteOverlay(buttons[i]); + // When our script shuts down, we should clean up all of our overlays + function scriptEnding() { + var notificationIndex; + for (notificationIndex = 0; notificationIndex < notifications.length; notificationIndex++) { + Overlays.deleteOverlay(notifications[notificationIndex]); + Overlays.deleteOverlay(buttons[notificationIndex]); + } + Menu.removeMenu(MENU_NAME); } - Menu.removeMenu(MENU_NAME); -} -function menuItemEvent(menuItem) { - if (menuItem === PLAY_NOTIFICATION_SOUNDS_MENU_ITEM) { - Settings.setValue(PLAY_NOTIFICATION_SOUNDS_SETTING, Menu.isOptionChecked(PLAY_NOTIFICATION_SOUNDS_MENU_ITEM)); - return; + function menuItemEvent(menuItem) { + if (menuItem === PLAY_NOTIFICATION_SOUNDS_MENU_ITEM) { + Settings.setValue(PLAY_NOTIFICATION_SOUNDS_SETTING, Menu.isOptionChecked(PLAY_NOTIFICATION_SOUNDS_MENU_ITEM)); + return; + } + var notificationType = NotificationType.getTypeFromMenuItem(menuItem); + if (notificationType !== notificationType.UNKNOWN) { + Settings.setValue(PLAY_NOTIFICATION_SOUNDS_TYPE_SETTING_PRE + notificationType, Menu.isOptionChecked(menuItem)); + } } - var notificationType = NotificationType.getTypeFromMenuItem(menuItem); - if (notificationType !== notificationType.UNKNOWN) { - Settings.setValue(PLAY_NOTIFICATION_SOUNDS_TYPE_SETTING_PRE + notificationType, Menu.isOptionChecked(menuItem)); - } -} -LODManager.LODDecreased.connect(function() { - var warningText = "\n" + - "Due to the complexity of the content, the \n" + - "level of detail has been decreased. " + - "You can now see: \n" + - LODManager.getLODFeedbackText(); + LODManager.LODDecreased.connect(function () { + var warningText = "\n" + + "Due to the complexity of the content, the \n" + + "level of detail has been decreased. " + + "You can now see: \n" + + LODManager.getLODFeedbackText(); - if (lodTextID === false) { - lodTextID = createNotification(warningText, NotificationType.LOD_WARNING); - } else { - Overlays.editOverlay(lodTextID, { text: warningText }); - } -}); + if (lodTextID === false) { + lodTextID = createNotification(warningText, NotificationType.LOD_WARNING); + } else { + Overlays.editOverlay(lodTextID, { text: warningText }); + } + }); -Controller.keyPressEvent.connect(keyPressEvent); -Controller.mousePressEvent.connect(mousePressEvent); -Controller.keyReleaseEvent.connect(keyReleaseEvent); -Script.update.connect(update); -Script.scriptEnding.connect(scriptEnding); -Menu.menuItemEvent.connect(menuItemEvent); -Window.domainConnectionRefused.connect(onDomainConnectionRefused); -Window.stillSnapshotTaken.connect(onSnapshotTaken); -Window.processingGifStarted.connect(processingGif); -Window.connectionAdded.connect(connectionAdded); -Window.connectionError.connect(connectionError); -Window.announcement.connect(onNotify); -Window.notifyEditError = onEditError; -Window.notify = onNotify; -Tablet.tabletNotification.connect(tabletNotification); -setup(); + Controller.keyPressEvent.connect(keyPressEvent); + Controller.mousePressEvent.connect(mousePressEvent); + Controller.keyReleaseEvent.connect(keyReleaseEvent); + Script.update.connect(update); + Script.scriptEnding.connect(scriptEnding); + Menu.menuItemEvent.connect(menuItemEvent); + Window.domainConnectionRefused.connect(onDomainConnectionRefused); + Window.stillSnapshotTaken.connect(onSnapshotTaken); + Window.processingGifStarted.connect(processingGif); + Window.connectionAdded.connect(connectionAdded); + Window.connectionError.connect(connectionError); + Window.announcement.connect(onNotify); + Window.notifyEditError = onEditError; + Window.notify = onNotify; + Tablet.tabletNotification.connect(tabletNotification); + setup(); }()); // END LOCAL_SCOPE diff --git a/scripts/system/tablet-goto.js b/scripts/system/tablet-goto.js index 8235b69c90..8ba19d18a8 100644 --- a/scripts/system/tablet-goto.js +++ b/scripts/system/tablet-goto.js @@ -1,4 +1,6 @@ "use strict"; +/*jslint vars:true, plusplus:true, forin:true*/ +/*global Window, Script, Tablet, HMD, Controller, Account, XMLHttpRequest, location, print*/ // // goto.js @@ -11,35 +13,12 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -(function() { // BEGIN LOCAL_SCOPE +(function () { // BEGIN LOCAL_SCOPE var gotoQmlSource = "TabletAddressDialog.qml"; var buttonName = "GOTO"; var onGotoScreen = false; var shouldActivateButton = false; - - function onClicked() { - if (onGotoScreen) { - // for toolbar-mode: go back to home screen, this will close the window. - tablet.gotoHomeScreen(); - } else { - shouldActivateButton = true; - tablet.loadQMLSource(gotoQmlSource); - onGotoScreen = true; - } - } - - function onScreenChanged(type, url) { - if (url === gotoQmlSource) { - onGotoScreen = true; - shouldActivateButton = true; - button.editProperties({isActive: shouldActivateButton}); - messagesWaiting(false); - } else { - shouldActivateButton = false; - onGotoScreen = false; - button.editProperties({isActive: shouldActivateButton}); - } - } + function ignore() { } var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); var NORMAL_ICON = "icons/tablet-icons/goto-i.svg"; @@ -58,10 +37,33 @@ }); } + function onClicked() { + if (onGotoScreen) { + // for toolbar-mode: go back to home screen, this will close the window. + tablet.gotoHomeScreen(); + } else { + shouldActivateButton = true; + tablet.loadQMLSource(gotoQmlSource); + onGotoScreen = true; + } + } + + function onScreenChanged(type, url) { + ignore(type); + if (url === gotoQmlSource) { + onGotoScreen = true; + shouldActivateButton = true; + button.editProperties({isActive: shouldActivateButton}); + messagesWaiting(false); + } else { + shouldActivateButton = false; + onGotoScreen = false; + button.editProperties({isActive: shouldActivateButton}); + } + } button.clicked.connect(onClicked); tablet.screenChanged.connect(onScreenChanged); - var METAVERSE_BASE = location.metaverseServerUrl; function request(options, callback) { // cb(error, responseOfCorrectContentType) of url. A subset of npm request. var httpRequest = new XMLHttpRequest(), key; // QT bug: apparently doesn't handle onload. Workaround using readyState. @@ -129,7 +131,7 @@ uri: url }, function (error, data) { if (error || (data.status !== 'success')) { - print("Error: unable to get", url, error || response.status); + print("Error: unable to get", url, error || data.status); return; } var didNotify = false; From c82bd67a1491fbf2adcbc25d1596c8c603bb43dc Mon Sep 17 00:00:00 2001 From: anshuman64 Date: Wed, 26 Apr 2017 09:07:40 -0700 Subject: [PATCH 28/76] Update BUILD_WIN.md --- BUILD_WIN.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/BUILD_WIN.md b/BUILD_WIN.md index e37bf27503..9df19e5525 100644 --- a/BUILD_WIN.md +++ b/BUILD_WIN.md @@ -12,9 +12,11 @@ Download and install the CMake 3.8.0-rc2 "win64-x64 Installer" from the [CMake W ###Step 3. Installing Qt -Download and install the [Qt 5.6.1 Installer](https://download.qt.io/official_releases/qt/5.6/5.6.1-1/qt-opensource-windows-x86-msvc2013_64-5.6.1-1.exe). Please note that the download file is large (850MB) and may take some time. +Download and install the [Qt Online Installer for Windows](https://www.qt.io/download-open-source/?hsCtaTracking=f977210e-de67-475f-a32b-65cec207fd03%7Cd62710cd-e1db-46aa-8d4d-2f1c1ffdacea#section-2). + +Select the following components on the installer: + -Make sure to select all components when going through the installer. ###Step 4. Setting Qt Environment Variable From 2d4ac0fc436b05a7452e847d2f387fe1f0a6dbb6 Mon Sep 17 00:00:00 2001 From: samcake Date: Wed, 26 Apr 2017 11:04:48 -0700 Subject: [PATCH 29/76] Bringing a fix to the ImageReader::read crash to stable --- .../model-networking/src/model-networking/TextureCache.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index 98b03eba1e..b8d3a51534 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -559,9 +559,12 @@ void ImageReader::read() { // Load the image into a gpu::Texture auto networkTexture = resource.staticCast(); texture.reset(networkTexture->getTextureLoader()(image, url)); - texture->setSource(url); if (texture) { + texture->setSource(url); texture->setFallbackTexture(networkTexture->getFallbackTexture()); + } else { + qCDebug(modelnetworking) << _url << "loading stopped; texture wasn't created"; + return; } auto textureCache = DependencyManager::get(); From ed6211fcb7bd4b0dc954abae13959593a146a89d Mon Sep 17 00:00:00 2001 From: Anshuman Dewangan Date: Wed, 26 Apr 2017 13:26:14 -0700 Subject: [PATCH 30/76] First commit --- BUILD_WIN.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/BUILD_WIN.md b/BUILD_WIN.md index 9df19e5525..c1ec381b52 100644 --- a/BUILD_WIN.md +++ b/BUILD_WIN.md @@ -12,11 +12,9 @@ Download and install the CMake 3.8.0-rc2 "win64-x64 Installer" from the [CMake W ###Step 3. Installing Qt -Download and install the [Qt Online Installer for Windows](https://www.qt.io/download-open-source/?hsCtaTracking=f977210e-de67-475f-a32b-65cec207fd03%7Cd62710cd-e1db-46aa-8d4d-2f1c1ffdacea#section-2). - -Select the following components on the installer: - +Download and install the [Qt 5.6.2 for Windows 64-bit (VS 2013)](https://www.qt.io/download-open-source/?hsCtaTracking=f977210e-de67-475f-a32b-65cec207fd03%7Cd62710cd-e1db-46aa-8d4d-2f1c1ffdacea#section-2). +Make sure to select all components when going through the installer. ###Step 4. Setting Qt Environment Variable From c13f63a4f4c57b3c1c6f2cb7e4e9b0dc952608db Mon Sep 17 00:00:00 2001 From: Anshuman Dewangan Date: Wed, 26 Apr 2017 14:47:03 -0700 Subject: [PATCH 31/76] Changed references from Qt 5.6.1 to 5.6.2 --- BUILD.md | 8 ++++---- BUILD_OSX.md | 10 +++------- BUILD_WIN.md | 6 +++--- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/BUILD.md b/BUILD.md index 547b79cb08..fc2359b057 100644 --- a/BUILD.md +++ b/BUILD.md @@ -1,7 +1,7 @@ ###Dependencies * [cmake](https://cmake.org/download/) ~> 3.3.2 -* [Qt](https://www.qt.io/download-open-source) ~> 5.6.1 +* [Qt](https://www.qt.io/download-open-source) ~> 5.6.2 * [OpenSSL](https://www.openssl.org/community/binaries.html) * IMPORTANT: Use the latest available version of OpenSSL to avoid security vulnerabilities. * [VHACD](https://github.com/virneo/v-hacd)(clone this repository)(Optional) @@ -46,8 +46,8 @@ This can either be entered directly into your shell session before you build or The path it needs to be set to will depend on where and how Qt5 was installed. e.g. - export QT_CMAKE_PREFIX_PATH=/usr/local/qt/5.6.1/clang_64/lib/cmake/ - export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.6.1-1/lib/cmake + export QT_CMAKE_PREFIX_PATH=/usr/local/qt/5.6.2/clang_64/lib/cmake/ + export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.6.2/lib/cmake export QT_CMAKE_PREFIX_PATH=/usr/local/opt/qt5/lib/cmake ####Generating build files @@ -64,7 +64,7 @@ Any variables that need to be set for CMake to find dependencies can be set as E For example, to pass the QT_CMAKE_PREFIX_PATH variable during build file generation: - cmake .. -DQT_CMAKE_PREFIX_PATH=/usr/local/qt/5.6.1/lib/cmake + cmake .. -DQT_CMAKE_PREFIX_PATH=/usr/local/qt/5.6.2/lib/cmake ####Finding Dependencies diff --git a/BUILD_OSX.md b/BUILD_OSX.md index 980263cbbc..fceddf0198 100644 --- a/BUILD_OSX.md +++ b/BUILD_OSX.md @@ -16,16 +16,12 @@ For OpenSSL installed via homebrew, set OPENSSL_ROOT_DIR: Note that this uses the version from the homebrew formula at the time of this writing, and the version in the path will likely change. ###Qt -You can use the online installer or the offline installer. +Download and install the [Qt 5.6.2 for macOS](https://www.qt.io/download-open-source/?hsCtaTracking=f977210e-de67-475f-a32b-65cec207fd03%7Cd62710cd-e1db-46aa-8d4d-2f1c1ffdacea#section-2). -* [Download the online installer](https://www.qt.io/download-open-source/#section-2) - * When it asks you to select components, select the following: - * Qt > Qt 5.6 - -* [Download the offline installer](https://download.qt.io/official_releases/qt/5.6/5.6.1-1/qt-opensource-mac-x64-clang-5.6.1-1.dmg) +Keep the default components checked when going through the installer. Once Qt is installed, you need to manually configure the following: -* Set the QT_CMAKE_PREFIX_PATH environment variable to your `Qt5.6.1/5.6/clang_64/lib/cmake/` directory. +* Set the QT_CMAKE_PREFIX_PATH environment variable to your `Qt5.6.2/5.6/clang_64/lib/cmake/` directory. ###Xcode If Xcode is your editor of choice, you can ask CMake to generate Xcode project files instead of Unix Makefiles. diff --git a/BUILD_WIN.md b/BUILD_WIN.md index c1ec381b52..0da05e4101 100644 --- a/BUILD_WIN.md +++ b/BUILD_WIN.md @@ -14,13 +14,13 @@ Download and install the CMake 3.8.0-rc2 "win64-x64 Installer" from the [CMake W Download and install the [Qt 5.6.2 for Windows 64-bit (VS 2013)](https://www.qt.io/download-open-source/?hsCtaTracking=f977210e-de67-475f-a32b-65cec207fd03%7Cd62710cd-e1db-46aa-8d4d-2f1c1ffdacea#section-2). -Make sure to select all components when going through the installer. +Keep the default components checked when going through the installer. ###Step 4. Setting Qt Environment Variable Go to "Control Panel > System > Advanced System Settings > Environment Variables > New..." (or search “Environment Variables” in Start Search). * Set "Variable name": QT_CMAKE_PREFIX_PATH -* Set "Variable value": `C:\Qt\Qt5.6.1\5.6\msvc2013_64\lib\cmake` +* Set "Variable value": `C:\Qt\Qt5.6.2\5.6\msvc2013_64\lib\cmake` ###Step 5. Installing OpenSSL @@ -77,5 +77,5 @@ If not, add the directory where nmake is located to the PATH environment variabl ####Qt is throwing an error -Make sure you have the correct version (5.6.1-1) installed and 'QT_CMAKE_PREFIX_PATH' environment variable is set correctly. +Make sure you have the correct version (5.6.2) installed and 'QT_CMAKE_PREFIX_PATH' environment variable is set correctly. From 54e4eb5eae364c994a9c92c8b441c90738484a6e Mon Sep 17 00:00:00 2001 From: Anshuman Dewangan Date: Wed, 26 Apr 2017 15:17:34 -0700 Subject: [PATCH 32/76] Updated downloads to direct links --- BUILD_OSX.md | 2 +- BUILD_WIN.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/BUILD_OSX.md b/BUILD_OSX.md index fceddf0198..afd3fa040c 100644 --- a/BUILD_OSX.md +++ b/BUILD_OSX.md @@ -16,7 +16,7 @@ For OpenSSL installed via homebrew, set OPENSSL_ROOT_DIR: Note that this uses the version from the homebrew formula at the time of this writing, and the version in the path will likely change. ###Qt -Download and install the [Qt 5.6.2 for macOS](https://www.qt.io/download-open-source/?hsCtaTracking=f977210e-de67-475f-a32b-65cec207fd03%7Cd62710cd-e1db-46aa-8d4d-2f1c1ffdacea#section-2). +Download and install the [Qt 5.6.2 for macOS](http://download.qt.io/official_releases/qt/5.6/5.6.2/qt-opensource-mac-x64-clang-5.6.2.dmg). Keep the default components checked when going through the installer. diff --git a/BUILD_WIN.md b/BUILD_WIN.md index 0da05e4101..54e84bb95c 100644 --- a/BUILD_WIN.md +++ b/BUILD_WIN.md @@ -8,11 +8,11 @@ Note: Newer versions of Visual Studio are not yet compatible. ###Step 2. Installing CMake -Download and install the CMake 3.8.0-rc2 "win64-x64 Installer" from the [CMake Website](https://cmake.org/download/). Make sure "Add CMake to system PATH for all users" is checked when going through the installer. +Download and install the [CMake 3.8.0 win64-x64 Installer](https://cmake.org/files/v3.8/cmake-3.8.0-win64-x64.msi). Make sure "Add CMake to system PATH for all users" is checked when going through the installer. ###Step 3. Installing Qt -Download and install the [Qt 5.6.2 for Windows 64-bit (VS 2013)](https://www.qt.io/download-open-source/?hsCtaTracking=f977210e-de67-475f-a32b-65cec207fd03%7Cd62710cd-e1db-46aa-8d4d-2f1c1ffdacea#section-2). +Download and install the [Qt 5.6.2 for Windows 64-bit (VS 2013)](http://download.qt.io/official_releases/qt/5.6/5.6.2/qt-opensource-windows-x86-msvc2013_64-5.6.2.exe). Keep the default components checked when going through the installer. @@ -24,7 +24,7 @@ Go to "Control Panel > System > Advanced System Settings > Environment Variables ###Step 5. Installing OpenSSL -Download and install the "Win64 OpenSSL v1.0.2k" Installer from [this website](https://slproweb.com/products/Win32OpenSSL.html). +Download and install the [Win64 OpenSSL v1.0.2k Installer](https://slproweb.com/download/Win64OpenSSL-1_0_2k.exe). ###Step 6. Running CMake to Generate Build Files From f795027ab70ae1141a21c7279d96bb9e376256a8 Mon Sep 17 00:00:00 2001 From: Triplelexx Date: Thu, 27 Apr 2017 01:09:04 +0100 Subject: [PATCH 33/76] change menu item locations AudioNoiseReduction moved under Audio. Avatar Collision under Avatar. --- interface/src/Menu.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index c99178d8cc..8754951317 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -156,6 +156,8 @@ Menu::Menu() { // Audio > Show Level Meter addCheckableActionToQMenuAndActionHash(audioMenu, MenuOption::AudioTools, 0, false); + addCheckableActionToQMenuAndActionHash(audioMenu, MenuOption::AudioNoiseReduction, 0, true, + audioIO.data(), SLOT(toggleAudioNoiseReduction())); // Avatar menu ---------------------------------- MenuWrapper* avatarMenu = addMenu("Avatar"); @@ -196,6 +198,9 @@ Menu::Menu() { 0, // QML Qt::Key_Apostrophe, qApp, SLOT(resetSensors())); + addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::EnableCharacterController, 0, true, + avatar.get(), SLOT(updateMotionBehaviorFromMenu())); + // Avatar > AvatarBookmarks related menus -- Note: the AvatarBookmarks class adds its own submenus here. auto avatarBookmarks = DependencyManager::get(); avatarBookmarks->setupMenus(this, avatarMenu); @@ -532,10 +537,6 @@ Menu::Menu() { avatar.get(), SLOT(updateMotionBehaviorFromMenu()), UNSPECIFIED_POSITION, "Developer"); - addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::EnableCharacterController, 0, true, - avatar.get(), SLOT(updateMotionBehaviorFromMenu()), - UNSPECIFIED_POSITION, "Developer"); - // Developer > Hands >>> MenuWrapper* handOptionsMenu = developerMenu->addMenu("Hands"); addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHandTargets, 0, false, @@ -622,8 +623,6 @@ Menu::Menu() { QString("../../hifi/tablet/TabletAudioPreferences.qml"), "AudioPreferencesDialog"); }); - addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::AudioNoiseReduction, 0, true, - audioIO.data(), SLOT(toggleAudioNoiseReduction())); addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::EchoServerAudio, 0, false, audioIO.data(), SLOT(toggleServerEcho())); addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::EchoLocalAudio, 0, false, From f9c6cb27f4466905042c5b4c2b44a6d1a62405ef Mon Sep 17 00:00:00 2001 From: Triplelexx Date: Thu, 27 Apr 2017 03:16:04 +0100 Subject: [PATCH 34/76] change menu item for avatar collisions to "Collide with world" --- interface/src/Menu.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/Menu.h b/interface/src/Menu.h index b6f70f5339..dd87a0cee3 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -96,7 +96,7 @@ namespace MenuOption { const QString DontRenderEntitiesAsScene = "Don't Render Entities as Scene"; const QString EchoLocalAudio = "Echo Local Audio"; const QString EchoServerAudio = "Echo Server Audio"; - const QString EnableCharacterController = "Enable avatar collisions"; + const QString EnableCharacterController = "Collide with world"; const QString EnableInverseKinematics = "Enable Inverse Kinematics"; const QString EntityScriptServerLog = "Entity Script Server Log"; const QString ExpandMyAvatarSimulateTiming = "Expand /myAvatar/simulation"; From a154879001208205daa81406d8a36905333f08fe Mon Sep 17 00:00:00 2001 From: Triplelexx Date: Thu, 27 Apr 2017 03:25:24 +0100 Subject: [PATCH 35/76] change menu item text Audio Noise Reduction to Noise Reduction --- interface/src/Menu.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/Menu.h b/interface/src/Menu.h index dd87a0cee3..eeffcac7ca 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -36,7 +36,7 @@ namespace MenuOption { const QString AssetMigration = "ATP Asset Migration"; const QString AssetServer = "Asset Browser"; const QString Attachments = "Attachments..."; - const QString AudioNoiseReduction = "Audio Noise Reduction"; + const QString AudioNoiseReduction = "Noise Reduction"; const QString AudioScope = "Show Scope"; const QString AudioScopeFiftyFrames = "Fifty"; const QString AudioScopeFiveFrames = "Five"; From 0d53b823261b0a9077d63fef23e3d12962f61de2 Mon Sep 17 00:00:00 2001 From: Vladyslav Stelmakhovskyi Date: Thu, 27 Apr 2017 09:30:00 +0200 Subject: [PATCH 36/76] Renamed EDIT to CREATE --- scripts/system/edit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 6fabeb2ec6..6fae4df64e 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -343,7 +343,7 @@ var toolBar = (function () { activeButton = tablet.addButton({ icon: "icons/tablet-icons/edit-i.svg", activeIcon: "icons/tablet-icons/edit-a.svg", - text: "EDIT", + text: "CREATE", sortOrder: 10 }); tablet.screenChanged.connect(function (type, url) { From 1916eafc41b658e535c4bc2a8dff7e4a06019aa7 Mon Sep 17 00:00:00 2001 From: Vladyslav Stelmakhovskyi Date: Thu, 27 Apr 2017 16:02:47 +0200 Subject: [PATCH 37/76] Trigger rebuild --- scripts/system/edit.js | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 6fae4df64e..a6d2d165f7 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -2094,3 +2094,4 @@ entityListTool.webView.webEventReceived.connect(function (data) { }); }()); // END LOCAL_SCOPE + From 521db82ed0ddc237e9e6a6608b31a6c5877175be Mon Sep 17 00:00:00 2001 From: Anshuman Dewangan Date: Thu, 27 Apr 2017 10:56:56 -0700 Subject: [PATCH 38/76] Use generic dir syntax --- BUILD_WIN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BUILD_WIN.md b/BUILD_WIN.md index 54e84bb95c..841cfba3b3 100644 --- a/BUILD_WIN.md +++ b/BUILD_WIN.md @@ -20,7 +20,7 @@ Keep the default components checked when going through the installer. Go to "Control Panel > System > Advanced System Settings > Environment Variables > New..." (or search “Environment Variables” in Start Search). * Set "Variable name": QT_CMAKE_PREFIX_PATH -* Set "Variable value": `C:\Qt\Qt5.6.2\5.6\msvc2013_64\lib\cmake` +* Set "Variable value": `%QT_DIR%\5.6\msvc2013_64\lib\cmake` ###Step 5. Installing OpenSSL From 63468cc9e1483d2c92ea1547fb6b763d3d0819de Mon Sep 17 00:00:00 2001 From: David Kelly Date: Thu, 27 Apr 2017 12:20:56 -0700 Subject: [PATCH 39/76] Thread safety for sending location --- interface/src/DiscoverabilityManager.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/interface/src/DiscoverabilityManager.cpp b/interface/src/DiscoverabilityManager.cpp index 98bfa9c0c7..36f6d8633e 100644 --- a/interface/src/DiscoverabilityManager.cpp +++ b/interface/src/DiscoverabilityManager.cpp @@ -23,6 +23,8 @@ #include "DiscoverabilityManager.h" #include "Menu.h" +#include + const Discoverability::Mode DEFAULT_DISCOVERABILITY_MODE = Discoverability::Friends; DiscoverabilityManager::DiscoverabilityManager() : @@ -37,6 +39,13 @@ const QString API_USER_HEARTBEAT_PATH = "/api/v1/user/heartbeat"; const QString SESSION_ID_KEY = "session_id"; void DiscoverabilityManager::updateLocation() { + // since we store the last location and compare it to + // the current one in this function, we need to do this in + // the object's main thread (or use a mutex) + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "updateLocation"); + return; + } auto accountManager = DependencyManager::get(); auto addressManager = DependencyManager::get(); auto& domainHandler = DependencyManager::get()->getDomainHandler(); @@ -143,7 +152,7 @@ void DiscoverabilityManager::removeLocation() { void DiscoverabilityManager::setDiscoverabilityMode(Discoverability::Mode discoverabilityMode) { if (static_cast(_mode.get()) != discoverabilityMode) { - + // update the setting to the new value _mode.set(static_cast(discoverabilityMode)); updateLocation(); // update right away From 56196c03a355beb7a32ea006de1b6ebbca789c06 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Thu, 27 Apr 2017 12:56:32 -0700 Subject: [PATCH 40/76] pr review --- interface/resources/qml/hifi/Card.qml | 2 +- interface/resources/qml/hifi/Feed.qml | 5 +++-- .../resources/qml/hifi/tablet/TabletAddressDialog.qml | 9 ++++++--- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/interface/resources/qml/hifi/Card.qml b/interface/resources/qml/hifi/Card.qml index 1a3ae43692..59fa66af0b 100644 --- a/interface/resources/qml/hifi/Card.qml +++ b/interface/resources/qml/hifi/Card.qml @@ -130,7 +130,7 @@ Rectangle { property int dropSamples: 9; property int dropSpread: 0; DropShadow { - visible: showPlace && desktop.gradientsSupported; + visible: showPlace; // Do we have to check for whatever the modern equivalent is for desktop.gradientsSupported? source: place; anchors.fill: place; horizontalOffset: dropHorizontalOffset; diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index c02cd1dd36..d95518b891 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -31,6 +31,7 @@ Column { property string labelText: actions; property string filter: ''; onFilterChanged: filterChoicesByText(); + property var goFunction: null; HifiConstants { id: hifi } ListModel { id: suggestions; } @@ -57,7 +58,7 @@ Column { created_at: data.created_at || "", action: data.action || "", thumbnail_url: resolveUrl(thumbnail_url), - image_url: resolveUrl(data.details.image_url), + image_url: resolveUrl(data.details && data.details.image_url), metaverseId: (data.id || "").toString(), // Some are strings from server while others are numbers. Model objects require uniformity. @@ -181,7 +182,7 @@ Column { delegate: Card { width: cardWidth; height: cardHeight; - goFunction: goCard; // fixme global + goFunction: root.goFunction; userName: model.username; placeName: model.place_name; hifiUrl: model.place_name + model.path; diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index ef120b9c4f..6695ac08f6 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -242,7 +242,7 @@ StackView { height: stack.height; color: "transparent"; anchors { - left: bgMain.left; + left: parent.left; leftMargin: column.pad; topMargin: column.pad; } @@ -258,9 +258,10 @@ StackView { cardHeight: places.cardHeight * happeningNow.cardScale; metaverseServerUrl: addressBarDialog.metaverseServerUrl; labelText: 'Happening Now'; - //actions: 'concurrency,snapshot'; // uncomment this line instead of next to produce fake announcement data for testing. - actions: 'announcement'; + actions: 'concurrency,snapshot'; // uncomment this line instead of next to produce fake announcement data for testing. + //actions: 'announcement'; filter: addressLine.text; + goFunction: goCard; } Feed { id: places; @@ -269,6 +270,7 @@ StackView { labelText: 'Places'; actions: 'concurrency'; filter: addressLine.text; + goFunction: goCard; } Feed { id: snapshots; @@ -277,6 +279,7 @@ StackView { labelText: 'Recent Activity'; actions: 'snapshot'; filter: addressLine.text; + goFunction: goCard; } } } From e4f259e6303dd64da07b47631fd0a6a3ab8237ec Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Thu, 27 Apr 2017 13:01:33 -0700 Subject: [PATCH 41/76] back to non-fake data --- interface/resources/qml/hifi/tablet/TabletAddressDialog.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index 6695ac08f6..5578b94168 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -258,8 +258,8 @@ StackView { cardHeight: places.cardHeight * happeningNow.cardScale; metaverseServerUrl: addressBarDialog.metaverseServerUrl; labelText: 'Happening Now'; - actions: 'concurrency,snapshot'; // uncomment this line instead of next to produce fake announcement data for testing. - //actions: 'announcement'; + //actions: 'concurrency,snapshot'; // uncomment this line instead of next to produce fake announcement data for testing. + actions: 'announcement'; filter: addressLine.text; goFunction: goCard; } From 0860352caacf4c7b60ddb491673516954c363f75 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 18 Apr 2017 14:44:24 -0700 Subject: [PATCH 42/76] Checkpoint before switching to crash duty --- scripts/system/html/SnapshotReview.html | 36 ++---- scripts/system/html/css/SnapshotReview.css | 140 +++++++++++++-------- scripts/system/html/css/hifi-style.css | 90 +++++++++++++ scripts/system/html/js/SnapshotReview.js | 6 + scripts/system/snapshot.js | 5 + 5 files changed, 200 insertions(+), 77 deletions(-) create mode 100644 scripts/system/html/css/hifi-style.css diff --git a/scripts/system/html/SnapshotReview.html b/scripts/system/html/SnapshotReview.html index 145cfb16a9..2a68217976 100644 --- a/scripts/system/html/SnapshotReview.html +++ b/scripts/system/html/SnapshotReview.html @@ -1,7 +1,7 @@ Share - + @@ -10,6 +10,9 @@
+ + +

@@ -17,29 +20,16 @@
-
-
-
- - - - -
-
-
- -
+
+
+
+ +
+ + +
-
-
- - - - - - - - +
diff --git a/scripts/system/html/css/SnapshotReview.css b/scripts/system/html/css/SnapshotReview.css index 34b690a021..058fe141dd 100644 --- a/scripts/system/html/css/SnapshotReview.css +++ b/scripts/system/html/css/SnapshotReview.css @@ -9,40 +9,66 @@ */ body { - padding-top: 0; - padding-bottom: 14px; + padding: 0; } +/* +// START styling of top bar and its contents +*/ .snapsection { - padding-top: 14px; - text-align: center; + padding-top: 12px; } .snapsection.title { padding-top: 0; text-align: left; + height: 24px; + clear: both; } .title label { - font-size: 18px; position: relative; - top: 12px; + top: 10px; + font-size: 18px; + float: left; } +#snapshotSettings { + position: relative; + top: 4px; + float: right; +} +#settingsLabel { + position: relative; + float: right; + top: 12px; + font-family: Raleway-SemiBold; + font-size: 14px; +} +.hifi-glyph { + font-size: 30px; +} +input[type=button].naked { + color: #afafaf; + background: none; +} +input[type=button].naked:hover { + color: #ffffff; +} +input[type=button].naked:active { + color: #afafaf; +} +/* +// END styling of top bar and its contents +*/ + +/* +// START styling of snapshot pane and its contents +*/ #snapshot-pane { - width: 100%; - height: 100%; - position: absolute; - top: 0; - left: 0; - box-sizing: border-box; - padding-top: 56px; - padding-bottom: 175px; } #snapshot-images { - height: 100%; - width: 100%; position: relative; } @@ -77,23 +103,59 @@ body { #snapshot-images img.multiple { padding-left: 28px; } +/* +// END styling of snapshot pane and its contents +*/ +/* +// START styling of snapshot controls (bottom panel) and its contents +*/ #snapshot-controls { width: 100%; position: absolute; left: 0; bottom: 14px; + margin-bottom: 14px; + overflow: hidden; +} +#snap-settings { + float: left; + margin-left: 10px; +} +#snap-settings label { + margin-bottom: 50px; +} +#snap-settings form input { + margin-bottom: 50px; } +#snap-button { + width: 65px; + height: 65px; + border-radius: 50%; + background: #EA4C5F; + border: 3px solid white; + margin-left: auto; + margin-right: auto; +} +#snap-button:hover { + background: #C62147; +} +#snap-button:active { + background: #EA4C5F; +} +/* +// END styling of snapshot controls (bottom panel) and its contents +*/ + +/* +// START misc styling +*/ .prompt { font-family: Raleway-SemiBold; font-size: 14px; } -div.button { - padding-top: 21px; -} - .compound-button { position: relative; height: auto; @@ -103,7 +165,7 @@ div.button { padding-left: 40px; } -.compound-button .glyph { +.compound-button { display: inline-block; position: absolute; left: 12px; @@ -114,36 +176,6 @@ div.button { background-repeat: no-repeat; background-size: 23px 23px; } - -.setting { - display: inline-table; - height: 28px; -} - -.setting label { - display: table-cell; - vertical-align: middle; - font-family: Raleway-SemiBold; - font-size: 14px; -} - -.setting + .setting { - margin-left: 18px; -} - -input[type=button].naked { - font-size: 40px; - line-height: 40px; - width: 30px; - padding: 0; - margin: 0 0 -6px 0; - position: relative; - top: -6px; - left: -8px; - background: none; -} - -input[type=button].naked:hover { - color: #00b4ef; - background: none; -} +/* +// END misc styling +*/ diff --git a/scripts/system/html/css/hifi-style.css b/scripts/system/html/css/hifi-style.css new file mode 100644 index 0000000000..52d4c72b23 --- /dev/null +++ b/scripts/system/html/css/hifi-style.css @@ -0,0 +1,90 @@ +/* +// hifi-style.css +// +// Created by Zach Fox on 2017-04-18 +// Copyright 2017 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 +*/ + +@font-face { + font-family: Raleway-Regular; + src: url(../../../../resources/fonts/Raleway-Regular.ttf), /* Windows production */ + url(../../../../fonts/Raleway-Regular.ttf), /* OSX production */ + url(../../../../interface/resources/fonts/Raleway-Regular.ttf); /* Development, running script in /HiFi/examples */ +} + +@font-face { + font-family: Raleway-Light; + src: url(../../../../resources/fonts/Raleway-Light.ttf), + url(../../../../fonts/Raleway-Light.ttf), + url(../../../../interface/resources/fonts/Raleway-Light.ttf); +} + +@font-face { + font-family: Raleway-Bold; + src: url(../../../../resources/fonts/Raleway-Bold.ttf), + url(../../../../fonts/Raleway-Bold.ttf), + url(../../../../interface/resources/fonts/Raleway-Bold.ttf); +} + +@font-face { + font-family: Raleway-SemiBold; + src: url(../../../../resources/fonts/Raleway-SemiBold.ttf), + url(../../../../fonts/Raleway-SemiBold.ttf), + url(../../../../interface/resources/fonts/Raleway-SemiBold.ttf); +} + +@font-face { + font-family: FiraSans-SemiBold; + src: url(../../../../resources/fonts/FiraSans-SemiBold.ttf), + url(../../../../fonts/FiraSans-SemiBold.ttf), + url(../../../../interface/resources/fonts/FiraSans-SemiBold.ttf); +} + +@font-face { + font-family: AnonymousPro-Regular; + src: url(../../../../resources/fonts/AnonymousPro-Regular.ttf), + url(../../../../fonts/AnonymousPro-Regular.ttf), + url(../../../../interface/resources/fonts/AnonymousPro-Regular.ttf); +} + +@font-face { + font-family: HiFi-Glyphs; + src: url(../../../../resources/fonts/hifi-glyphs.ttf), + url(../../../../fonts/hifi-glyphs.ttf), + url(../../../../interface/resources/fonts/hifi-glyphs.ttf); +} + +body { + color: #afafaf; + background-color: #404040; + font-family: Raleway-Regular; + font-size: 15px; + + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + + overflow-x: hidden; + overflow-y: auto; +} + +hr { + border: none; + background: #404040 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAjSURBVBhXY1RVVf3PgARYjIyMoEwIYHRwcEBRwQSloYCBAQCwjgPMiI7W2QAAAABJRU5ErkJggg==) repeat-x top left; + width: 100%; + margin: 21px -21px 0 -21px; + padding: 14px 21px 0 21px; +} + +.hifi-glyph { + font-family: HiFi-Glyphs; + border: none; + //margin: -10px; + padding: 0; +} diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index f140c54e09..7fec372c94 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -124,3 +124,9 @@ function snapshotSettings() { action: "openSettings" })); } +function takeSnapshot() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "takeSnapshot" + })); +} diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 6039bc09c1..47abf41021 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -75,6 +75,11 @@ function onMessage(message) { tablet.loadQMLOnTop("TabletGeneralPreferences.qml"); } break; + case 'takeSnapshot': + // In settings, first store the paths to the last snapshot + // + onClicked(); + break; case 'setOpenFeedFalse': Settings.setValue('openFeedAfterShare', false); break; From 7acdc866440db2c95744de2210d256796d6aa582 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 19 Apr 2017 13:55:47 -0700 Subject: [PATCH 43/76] Progress --- scripts/system/html/SnapshotReview.html | 2 +- scripts/system/html/css/SnapshotReview.css | 33 +------ scripts/system/html/js/SnapshotReview.js | 102 +++++++++++---------- scripts/system/snapshot.js | 7 ++ 4 files changed, 67 insertions(+), 77 deletions(-) diff --git a/scripts/system/html/SnapshotReview.html b/scripts/system/html/SnapshotReview.html index 2a68217976..1dff573014 100644 --- a/scripts/system/html/SnapshotReview.html +++ b/scripts/system/html/SnapshotReview.html @@ -23,7 +23,7 @@

- +
diff --git a/scripts/system/html/css/SnapshotReview.css b/scripts/system/html/css/SnapshotReview.css index 058fe141dd..3985253a03 100644 --- a/scripts/system/html/css/SnapshotReview.css +++ b/scripts/system/html/css/SnapshotReview.css @@ -10,6 +10,7 @@ body { padding: 0; + margin: 0; } /* @@ -17,6 +18,7 @@ body { */ .snapsection { padding-top: 12px; + margin: 8px; } .snapsection.title { @@ -66,43 +68,18 @@ input[type=button].naked:active { // START styling of snapshot pane and its contents */ #snapshot-pane { + height: 510px; } #snapshot-images { position: relative; } -#snapshot-images > div { - position: relative; - text-align: center; -} - #snapshot-images img { max-width: 100%; max-height: 100%; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); vertical-align: middle; } - -#snapshot-images div.property { - margin-top: 0; - position: absolute; - top: 50%; - left: 7px; - transform: translate(0%, -50%); -} - -#snapshot-images img { - box-sizing: border-box; - padding: 0 7px 0 7px; -} - -#snapshot-images img.multiple { - padding-left: 28px; -} /* // END styling of snapshot pane and its contents */ @@ -123,10 +100,10 @@ input[type=button].naked:active { margin-left: 10px; } #snap-settings label { - margin-bottom: 50px; + height: 50px; } #snap-settings form input { - margin-bottom: 50px; + margin-bottom: 10px; } #snap-button { diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index 7fec372c94..4b95507309 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -10,43 +10,44 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -var paths = [], idCounter = 0, imageCount; +var paths = [], idCounter = 0, imageCount = 1; function addImage(data) { if (!data.localPath) { return; } - var div = document.createElement("DIV"), - input = document.createElement("INPUT"), - label = document.createElement("LABEL"), - img = document.createElement("IMG"), - div2 = document.createElement("DIV"), - id = "p" + idCounter++; - img.id = id + "img"; - function toggle() { data.share = input.checked; } + var div = document.createElement("DIV"); + var id = "p" + idCounter++; + var img = document.createElement("IMG"); + img.id = "img" + id; div.style.height = "" + Math.floor(100 / imageCount) + "%"; if (imageCount > 1) { img.setAttribute("class", "multiple"); } img.src = data.localPath; div.appendChild(img); - if (imageCount > 1) { // I'd rather use css, but the included stylesheet is quite particular. - // Our stylesheet(?) requires input.id to match label.for. Otherwise input doesn't display the check state. - label.setAttribute('for', id); // cannot do label.for = - input.id = id; - input.type = "checkbox"; - input.checked = false; - data.share = input.checked; - input.addEventListener('change', toggle); - div2.setAttribute("class", "property checkbox"); - div2.appendChild(input); - div2.appendChild(label); - div.appendChild(div2); - } else { - data.share = true; - } document.getElementById("snapshot-images").appendChild(div); paths.push(data); } +function handleCaptureSetting(setting) { + var stillAndGif = document.getElementById('stillAndGif'); + var stillOnly = document.getElementById('stillOnly'); + stillAndGif.checked = setting; + stillOnly.checked = !setting; + + stillAndGif.onclick = function () { + EventBridge.emitWebEvent(JSON.stringify({ + type: "captureSettings", + action: true + })); + } + stillOnly.onclick = function () { + EventBridge.emitWebEvent(JSON.stringify({ + type: "captureSettings", + action: false + })); + } + +} function handleShareButtons(messageOptions) { var openFeed = document.getElementById('openFeed'); openFeed.checked = messageOptions.openFeedAfterShare; @@ -66,36 +67,41 @@ function handleShareButtons(messageOptions) { window.onload = function () { // Something like the following will allow testing in a browser. //addImage({localPath: 'c:/Users/howar/OneDrive/Pictures/hifi-snap-by--on-2016-07-27_12-58-43.jpg'}); - //addImage({ localPath: 'http://lorempixel.com/1512/1680' }); + addImage({ localPath: 'http://lorempixel.com/1512/1680' }); openEventBridge(function () { // Set up a handler for receiving the data, and tell the .js we are ready to receive it. EventBridge.scriptEventReceived.connect(function (message) { + message = JSON.parse(message); - if (message.type !== "snapshot") { - return; - } - // The last element of the message contents list contains a bunch of options, - // including whether or not we can share stuff - // The other elements of the list contain image paths. - var messageOptions = message.action.pop(); - handleShareButtons(messageOptions); + switch (message.type) { + case 'snapshot': + // The last element of the message contents list contains a bunch of options, + // including whether or not we can share stuff + // The other elements of the list contain image paths. + var messageOptions = message.action.pop(); + handleShareButtons(messageOptions); - if (messageOptions.containsGif) { - if (messageOptions.processingGif) { - imageCount = message.action.length + 1; // "+1" for the GIF that'll finish processing soon - message.action.unshift({ localPath: messageOptions.loadingGifPath }); - message.action.forEach(addImage); - document.getElementById('p0').disabled = true; - } else { - var gifPath = message.action[0].localPath; - document.getElementById('p0').disabled = false; - document.getElementById('p0img').src = gifPath; - paths[0].localPath = gifPath; - } - } else { - imageCount = message.action.length; - message.action.forEach(addImage); + if (messageOptions.containsGif) { + if (messageOptions.processingGif) { + imageCount = message.action.length + 1; // "+1" for the GIF that'll finish processing soon + message.action.unshift({ localPath: messageOptions.loadingGifPath }); + message.action.forEach(addImage); + } else { + var gifPath = message.action[0].localPath; + document.getElementById('imgp0').src = gifPath; + paths[0].localPath = gifPath; + } + } else { + imageCount = message.action.length; + message.action.forEach(addImage); + } + break; + case 'snapshotSettings': + handleCaptureSetting(message.action); + break; + default: + return; } }); EventBridge.emitWebEvent(JSON.stringify({ diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 47abf41021..266f90c704 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -61,6 +61,10 @@ function onMessage(message) { var needsLogin = false; switch (message.action) { case 'ready': // Send it. + tablet.emitScriptEvent(JSON.stringify({ + type: "snapshotSettings", + action: Settings.getValue("alsoTakeAnimatedSnapshot", true) + })); tablet.emitScriptEvent(JSON.stringify({ type: "snapshot", action: readyData @@ -75,6 +79,9 @@ function onMessage(message) { tablet.loadQMLOnTop("TabletGeneralPreferences.qml"); } break; + case 'captureSettings': + Settings.setValue("alsoTakeAnimatedSnapshot", message.action); + break; case 'takeSnapshot': // In settings, first store the paths to the last snapshot // From a4f428a6e666239d5e6728ab637b2ad4bbcf9855 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 19 Apr 2017 15:27:10 -0700 Subject: [PATCH 44/76] Lots of progress --- scripts/system/html/js/SnapshotReview.js | 41 ++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index 4b95507309..10d5b0eb92 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -19,15 +19,47 @@ function addImage(data) { var id = "p" + idCounter++; var img = document.createElement("IMG"); img.id = "img" + id; + div.style.width = "100%"; div.style.height = "" + Math.floor(100 / imageCount) + "%"; + div.style.display = "flex"; + div.style.justifyContent = "center"; + div.style.alignItems = "center"; + div.style.marginBottom = "5px"; + div.style.position = "relative"; if (imageCount > 1) { img.setAttribute("class", "multiple"); } img.src = data.localPath; div.appendChild(img); + div.appendChild(createShareOverlayDiv()); document.getElementById("snapshot-images").appendChild(div); paths.push(data); } +function createShareOverlayDiv() { + var div = document.createElement("DIV"); + div.style.position = "absolute"; + div.style.display = "flex"; + div.style.alignItems = "flex-end"; + div.style.top = "0px"; + div.style.left = "0px"; + div.style.width = "100%"; + div.style.height = "100%"; + + var shareBar = document.createElement("div"); + shareBar.style.backgroundColor = "black"; + shareBar.style.opacity = "0.5"; + shareBar.style.width = "100%"; + shareBar.style.height = "50px"; + div.appendChild(shareBar); + + var shareOverlay = document.createElement("div"); + shareOverlay.style.display = "none"; + shareOverlay.style.backgroundColor = "black"; + shareOverlay.style.opacity = "0.5"; + div.appendChild(shareOverlay); + + return div; +} function handleCaptureSetting(setting) { var stillAndGif = document.getElementById('stillAndGif'); var stillOnly = document.getElementById('stillOnly'); @@ -65,9 +97,14 @@ function handleShareButtons(messageOptions) { } } window.onload = function () { - // Something like the following will allow testing in a browser. - //addImage({localPath: 'c:/Users/howar/OneDrive/Pictures/hifi-snap-by--on-2016-07-27_12-58-43.jpg'}); + // TESTING FUNCTIONS START + // Uncomment and modify the lines below to test SnapshotReview in a browser. + imageCount = 2; addImage({ localPath: 'http://lorempixel.com/1512/1680' }); + addImage({ localPath: 'http://lorempixel.com/553/255' }); + //addImage({localPath: 'c:/Users/howar/OneDrive/Pictures/hifi-snap-by--on-2016-07-27_12-58-43.jpg'}); + // TESTING FUNCTIONS END + openEventBridge(function () { // Set up a handler for receiving the data, and tell the .js we are ready to receive it. EventBridge.scriptEventReceived.connect(function (message) { From 0b4cd41d7558c13b67ebadfb346c81f4a82752ad Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 19 Apr 2017 16:44:16 -0700 Subject: [PATCH 45/76] More and more progress! --- scripts/system/html/css/SnapshotReview.css | 38 ++++++++++++++++++++++ scripts/system/html/js/SnapshotReview.js | 33 ++++++++++++++----- 2 files changed, 62 insertions(+), 9 deletions(-) diff --git a/scripts/system/html/css/SnapshotReview.css b/scripts/system/html/css/SnapshotReview.css index 3985253a03..636edd9756 100644 --- a/scripts/system/html/css/SnapshotReview.css +++ b/scripts/system/html/css/SnapshotReview.css @@ -80,6 +80,44 @@ input[type=button].naked:active { max-height: 100%; vertical-align: middle; } + +.gifLabel { + font-family: Raleway-SemiBold; + font-size: 18px; + color: white; + float: left; + text-shadow: 2px 2px 3px #000000; + margin-left: 20px; +} +.shareButtonDiv { + display: flex; + align-items: center; + font-family: Raleway-SemiBold; + font-size: 14px; + color: white; + float: right; + text-shadow: 2px 2px 3px #000000; + width: 100px; + height: 100%; + margin-right: 10px; +} +.shareButtonLabel { + vertical-align: middle; +} +.shareButton { + background-color: white; + width: 40px; + height: 40px; + border-radius: 50%; + border-width: 0; + margin-left: 5px; +} +.shareButton:hover { + background-color: #afafaf; +} +.shareButton:active { + background-color: white; +} /* // END styling of snapshot pane and its contents */ diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index 10d5b0eb92..b0c53be502 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -18,7 +18,7 @@ function addImage(data) { var div = document.createElement("DIV"); var id = "p" + idCounter++; var img = document.createElement("IMG"); - img.id = "img" + id; + img.id = id + "img"; div.style.width = "100%"; div.style.height = "" + Math.floor(100 / imageCount) + "%"; div.style.display = "flex"; @@ -31,11 +31,16 @@ function addImage(data) { } img.src = data.localPath; div.appendChild(img); - div.appendChild(createShareOverlayDiv()); document.getElementById("snapshot-images").appendChild(div); + div.appendChild(createShareOverlayDiv(id, img.src.split('.').pop().toLowerCase() === "gif")); + img.onload = function () { + var shareBar = document.getElementById(id + "shareBar"); + shareBar.style.width = img.clientWidth; + shareBar.style.display = "inline"; + } paths.push(data); } -function createShareOverlayDiv() { +function createShareOverlayDiv(parentID, isGif) { var div = document.createElement("DIV"); div.style.position = "absolute"; div.style.display = "flex"; @@ -46,14 +51,24 @@ function createShareOverlayDiv() { div.style.height = "100%"; var shareBar = document.createElement("div"); - shareBar.style.backgroundColor = "black"; - shareBar.style.opacity = "0.5"; + shareBar.id = parentID + "shareBar" + shareBar.style.display = "none"; shareBar.style.width = "100%"; - shareBar.style.height = "50px"; + shareBar.style.height = "60px"; + shareBar.style.lineHeight = "60px"; + shareBar.style.clear = "both"; + shareBar.style.marginLeft = "auto"; + shareBar.style.marginRight = "auto"; + shareBar.innerHTML = isGif ? 'GIF' : ""; + var shareButtonID = parentID + "shareButton"; + shareBar.innerHTML += '
' + + '' + + '' + + '
' div.appendChild(shareBar); var shareOverlay = document.createElement("div"); - shareOverlay.style.display = "none"; + shareOverlay.style.visibilty = "hidden"; shareOverlay.style.backgroundColor = "black"; shareOverlay.style.opacity = "0.5"; div.appendChild(shareOverlay); @@ -100,7 +115,7 @@ window.onload = function () { // TESTING FUNCTIONS START // Uncomment and modify the lines below to test SnapshotReview in a browser. imageCount = 2; - addImage({ localPath: 'http://lorempixel.com/1512/1680' }); + addImage({ localPath: 'D:/Dropbox/Screenshots/High Fidelity Snapshots/2017-01-27 50 Avatars!/!!!.gif' }); addImage({ localPath: 'http://lorempixel.com/553/255' }); //addImage({localPath: 'c:/Users/howar/OneDrive/Pictures/hifi-snap-by--on-2016-07-27_12-58-43.jpg'}); // TESTING FUNCTIONS END @@ -126,7 +141,7 @@ window.onload = function () { message.action.forEach(addImage); } else { var gifPath = message.action[0].localPath; - document.getElementById('imgp0').src = gifPath; + document.getElementById('p0img').src = gifPath; paths[0].localPath = gifPath; } } else { From 4270086a2250bc89cfbe9b65987a2bb1a1109251 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 19 Apr 2017 17:47:12 -0700 Subject: [PATCH 46/76] Support 1 and 2 images --- scripts/system/html/css/SnapshotReview.css | 13 ++++-- scripts/system/html/js/SnapshotReview.js | 52 +++++++++++++++------- 2 files changed, 46 insertions(+), 19 deletions(-) diff --git a/scripts/system/html/css/SnapshotReview.css b/scripts/system/html/css/SnapshotReview.css index 636edd9756..9cbc129ab6 100644 --- a/scripts/system/html/css/SnapshotReview.css +++ b/scripts/system/html/css/SnapshotReview.css @@ -17,6 +17,8 @@ body { // START styling of top bar and its contents */ .snapsection { + padding-left: 8px; + padding-right: 8px; padding-top: 12px; margin: 8px; } @@ -68,17 +70,20 @@ input[type=button].naked:active { // START styling of snapshot pane and its contents */ #snapshot-pane { + width: 100%; height: 510px; + display: flex; + justify-content: center; + align-items: center; } #snapshot-images { - position: relative; + width: 100%; } #snapshot-images img { max-width: 100%; max-height: 100%; - vertical-align: middle; } .gifLabel { @@ -126,11 +131,13 @@ input[type=button].naked:active { // START styling of snapshot controls (bottom panel) and its contents */ #snapshot-controls { + padding-left: 8px; + padding-right: 8px; width: 100%; position: absolute; left: 0; bottom: 14px; - margin-bottom: 14px; + margin-bottom: 4px; overflow: hidden; } #snap-settings { diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index b0c53be502..3ebff3f687 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -18,13 +18,13 @@ function addImage(data) { var div = document.createElement("DIV"); var id = "p" + idCounter++; var img = document.createElement("IMG"); + div.id = id; img.id = id + "img"; div.style.width = "100%"; div.style.height = "" + Math.floor(100 / imageCount) + "%"; div.style.display = "flex"; div.style.justifyContent = "center"; div.style.alignItems = "center"; - div.style.marginBottom = "5px"; div.style.position = "relative"; if (imageCount > 1) { img.setAttribute("class", "multiple"); @@ -32,23 +32,26 @@ function addImage(data) { img.src = data.localPath; div.appendChild(img); document.getElementById("snapshot-images").appendChild(div); - div.appendChild(createShareOverlayDiv(id, img.src.split('.').pop().toLowerCase() === "gif")); + div.appendChild(createShareOverlay(id, img.src.split('.').pop().toLowerCase() === "gif")); img.onload = function () { var shareBar = document.getElementById(id + "shareBar"); shareBar.style.width = img.clientWidth; shareBar.style.display = "inline"; + + document.getElementById(id).style.height = img.clientHeight; } paths.push(data); } -function createShareOverlayDiv(parentID, isGif) { - var div = document.createElement("DIV"); - div.style.position = "absolute"; - div.style.display = "flex"; - div.style.alignItems = "flex-end"; - div.style.top = "0px"; - div.style.left = "0px"; - div.style.width = "100%"; - div.style.height = "100%"; +function createShareOverlay(parentID, isGif) { + var shareOverlayContainer = document.createElement("DIV"); + shareOverlayContainer.id = parentID + "shareOverlayContainer"; + shareOverlayContainer.style.position = "absolute"; + shareOverlayContainer.style.top = "0px"; + shareOverlayContainer.style.left = "0px"; + shareOverlayContainer.style.display = "flex"; + shareOverlayContainer.style.alignItems = "flex-end"; + shareOverlayContainer.style.width = "100%"; + shareOverlayContainer.style.height = "100%"; var shareBar = document.createElement("div"); shareBar.id = parentID + "shareBar" @@ -63,18 +66,35 @@ function createShareOverlayDiv(parentID, isGif) { var shareButtonID = parentID + "shareButton"; shareBar.innerHTML += '
' + '' + - '' + + '' + '
' - div.appendChild(shareBar); + shareOverlayContainer.appendChild(shareBar); var shareOverlay = document.createElement("div"); - shareOverlay.style.visibilty = "hidden"; + shareOverlay.id = parentID + "shareOverlay"; + shareOverlay.style.display = "none"; shareOverlay.style.backgroundColor = "black"; shareOverlay.style.opacity = "0.5"; - div.appendChild(shareOverlay); + shareOverlay.style.width = "100%"; + shareOverlay.style.height = "100%"; + shareOverlayContainer.appendChild(shareOverlay); - return div; + return shareOverlayContainer; } +function selectImageToShare(selectedID) { + selectedID = selectedID.id; // Why is this necessary? + var shareOverlayContainer = document.getElementById(selectedID + "shareOverlayContainer"); + var shareBar = document.getElementById(selectedID + "shareBar"); + var shareOverlay = document.getElementById(selectedID + "shareOverlay"); + + shareOverlayContainer.style.outline = "4px solid #00b4ef"; + shareOverlayContainer.style.outlineOffset = "-4px"; + + shareBar.style.display = "none"; + + shareOverlay.style.display = "inline"; +} + function handleCaptureSetting(setting) { var stillAndGif = document.getElementById('stillAndGif'); var stillOnly = document.getElementById('stillOnly'); From 2880b22f9fa28ecfadf5e47d39d60faede33af81 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 20 Apr 2017 14:00:28 -0700 Subject: [PATCH 47/76] External share buttons! --- scripts/system/html/SnapshotReview.html | 19 +++++++- scripts/system/html/css/SnapshotReview.css | 33 +++++++++++++ scripts/system/html/js/SnapshotReview.js | 54 ++++++++++++++++++++-- 3 files changed, 101 insertions(+), 5 deletions(-) diff --git a/scripts/system/html/SnapshotReview.html b/scripts/system/html/SnapshotReview.html index 1dff573014..3d8387890a 100644 --- a/scripts/system/html/SnapshotReview.html +++ b/scripts/system/html/SnapshotReview.html @@ -5,12 +5,29 @@ +
-
diff --git a/scripts/system/html/css/SnapshotReview.css b/scripts/system/html/css/SnapshotReview.css index 9cbc129ab6..a0602b2575 100644 --- a/scripts/system/html/css/SnapshotReview.css +++ b/scripts/system/html/css/SnapshotReview.css @@ -123,6 +123,39 @@ input[type=button].naked:active { .shareButton:active { background-color: white; } + +/* +// START styling of share overlay +*/ +.shareOverlayDiv { + text-align: center; +} +.shareControls { + text-align: left; + display: flex; + justify-content: center; + flex-direction: row; + align-items: flex-start; + height: 50px; +} +.shareOverlayLabel { + line-height: 75px; +} +.hifiShareControls { + text-align: left; + width: 40%; + margin-left: 10%; +} +.externalShareControls { + text-align: left; + width: 40%; + margin-right: 10%; +} +.cancelShare { +} +/* +// END styling of share overlay +*/ /* // END styling of snapshot pane and its contents */ diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index 3ebff3f687..21affd772b 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -70,14 +70,44 @@ function createShareOverlay(parentID, isGif) { '
' shareOverlayContainer.appendChild(shareBar); + var shareOverlayBackground = document.createElement("div"); + shareOverlayBackground.id = parentID + "shareOverlayBackground"; + shareOverlayBackground.style.display = "none"; + shareOverlayBackground.style.position = "absolute"; + shareOverlayBackground.style.zIndex = "1"; + shareOverlayBackground.style.top = "0px"; + shareOverlayBackground.style.left = "0px"; + shareOverlayBackground.style.backgroundColor = "black"; + shareOverlayBackground.style.opacity = "0.5"; + shareOverlayBackground.style.width = "100%"; + shareOverlayBackground.style.height = "100%"; + shareOverlayContainer.appendChild(shareOverlayBackground); + var shareOverlay = document.createElement("div"); shareOverlay.id = parentID + "shareOverlay"; + shareOverlay.className = "shareOverlayDiv"; shareOverlay.style.display = "none"; - shareOverlay.style.backgroundColor = "black"; - shareOverlay.style.opacity = "0.5"; shareOverlay.style.width = "100%"; shareOverlay.style.height = "100%"; + shareOverlay.style.zIndex = "2"; + var shareWithEveryoneButtonID = parentID + "shareWithEveryoneButton"; + var inviteConnectionsCheckboxID = parentID + "inviteConnectionsCheckbox"; + shareOverlay.innerHTML = '' + + '
' + + '
' + + '
' + + '
' + + '' + + '
' + + '' + + '
' + + '
' + + '' + + '' + + '
' + + '
'; shareOverlayContainer.appendChild(shareOverlay); + twttr.widgets.load(shareOverlay); return shareOverlayContainer; } @@ -85,15 +115,31 @@ function selectImageToShare(selectedID) { selectedID = selectedID.id; // Why is this necessary? var shareOverlayContainer = document.getElementById(selectedID + "shareOverlayContainer"); var shareBar = document.getElementById(selectedID + "shareBar"); + var shareOverlayBackground = document.getElementById(selectedID + "shareOverlayBackground"); var shareOverlay = document.getElementById(selectedID + "shareOverlay"); - shareOverlayContainer.style.outline = "4px solid #00b4ef"; - shareOverlayContainer.style.outlineOffset = "-4px"; + shareOverlay.style.outline = "4px solid #00b4ef"; + shareOverlay.style.outlineOffset = "-4px"; shareBar.style.display = "none"; + shareOverlayBackground.style.display = "inline"; shareOverlay.style.display = "inline"; } +function cancelSharing(selectedID) { + selectedID = selectedID.id; // Why is this necessary? + var shareOverlayContainer = document.getElementById(selectedID + "shareOverlayContainer"); + var shareBar = document.getElementById(selectedID + "shareBar"); + var shareOverlayBackground = document.getElementById(selectedID + "shareOverlayBackground"); + var shareOverlay = document.getElementById(selectedID + "shareOverlay"); + + shareOverlay.style.outline = "none"; + + shareBar.style.display = "inline"; + + shareOverlayBackground.style.display = "none"; + shareOverlay.style.display = "none"; +} function handleCaptureSetting(setting) { var stillAndGif = document.getElementById('stillAndGif'); From 16b4af8a9d8f581af5869f6d383511094d4a4be1 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 20 Apr 2017 16:25:21 -0700 Subject: [PATCH 48/76] Rearchitecture --- interface/src/Application.cpp | 2 +- scripts/system/html/SnapshotReview.html | 4 +- scripts/system/html/css/SnapshotReview.css | 25 ++---- scripts/system/html/css/hifi-style.css | 8 +- scripts/system/html/js/SnapshotReview.js | 63 ++++++------- scripts/system/snapshot.js | 100 +++++++++++---------- 6 files changed, 95 insertions(+), 107 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 39a4b8ee7c..d5e1d2201b 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -6448,7 +6448,7 @@ void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRa // Get a screenshot and save it QString path = Snapshot::saveSnapshot(getActiveDisplayPlugin()->getScreenshot(aspectRatio)); // If we're not doing an animated snapshot as well... - if (!includeAnimated || !(SnapshotAnimated::alsoTakeAnimatedSnapshot.get())) { + if (!includeAnimated) { // Tell the dependency manager that the capture of the still snapshot has taken place. emit DependencyManager::get()->stillSnapshotTaken(path, notify); } else { diff --git a/scripts/system/html/SnapshotReview.html b/scripts/system/html/SnapshotReview.html index 3d8387890a..e785fde69d 100644 --- a/scripts/system/html/SnapshotReview.html +++ b/scripts/system/html/SnapshotReview.html @@ -26,7 +26,7 @@ -
+
@@ -40,7 +40,7 @@

- +
diff --git a/scripts/system/html/css/SnapshotReview.css b/scripts/system/html/css/SnapshotReview.css index a0602b2575..58866feef0 100644 --- a/scripts/system/html/css/SnapshotReview.css +++ b/scripts/system/html/css/SnapshotReview.css @@ -16,41 +16,34 @@ body { /* // START styling of top bar and its contents */ -.snapsection { - padding-left: 8px; - padding-right: 8px; - padding-top: 12px; - margin: 8px; -} -.snapsection.title { - padding-top: 0; +.title { + padding: 6px 10px; text-align: left; - height: 24px; + height: 20px; + line-height: 20px; clear: both; } .title label { position: relative; - top: 10px; font-size: 18px; float: left; } #snapshotSettings { position: relative; - top: 4px; float: right; } #settingsLabel { position: relative; float: right; - top: 12px; font-family: Raleway-SemiBold; font-size: 14px; } .hifi-glyph { font-size: 30px; + top: -7px; } input[type=button].naked { color: #afafaf; @@ -71,7 +64,7 @@ input[type=button].naked:active { */ #snapshot-pane { width: 100%; - height: 510px; + height: 574px; display: flex; justify-content: center; align-items: center; @@ -169,17 +162,13 @@ input[type=button].naked:active { width: 100%; position: absolute; left: 0; - bottom: 14px; - margin-bottom: 4px; + margin-top: 8px; overflow: hidden; } #snap-settings { float: left; margin-left: 10px; } -#snap-settings label { - height: 50px; -} #snap-settings form input { margin-bottom: 10px; } diff --git a/scripts/system/html/css/hifi-style.css b/scripts/system/html/css/hifi-style.css index 52d4c72b23..e95ceca4da 100644 --- a/scripts/system/html/css/hifi-style.css +++ b/scripts/system/html/css/hifi-style.css @@ -77,9 +77,13 @@ body { hr { border: none; background: #404040 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAjSURBVBhXY1RVVf3PgARYjIyMoEwIYHRwcEBRwQSloYCBAQCwjgPMiI7W2QAAAABJRU5ErkJggg==) repeat-x top left; + padding: 1px; + -webkit-margin-before: 0; + -webkit-margin-after: 0; + -webkit-margin-start: 0; + -webkit-margin-end: 0; width: 100%; - margin: 21px -21px 0 -21px; - padding: 14px 21px 0 21px; + position: absolute; } .hifi-glyph { diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index 21affd772b..3346635a6a 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -149,40 +149,25 @@ function handleCaptureSetting(setting) { stillAndGif.onclick = function () { EventBridge.emitWebEvent(JSON.stringify({ - type: "captureSettings", - action: true + type: "snapshot", + action: "captureStillAndGif" })); } stillOnly.onclick = function () { - EventBridge.emitWebEvent(JSON.stringify({ - type: "captureSettings", - action: false - })); - } - -} -function handleShareButtons(messageOptions) { - var openFeed = document.getElementById('openFeed'); - openFeed.checked = messageOptions.openFeedAfterShare; - openFeed.onchange = function () { EventBridge.emitWebEvent(JSON.stringify({ type: "snapshot", - action: (openFeed.checked ? "setOpenFeedTrue" : "setOpenFeedFalse") + action: "captureStillOnly" })); - }; - - if (!messageOptions.canShare) { - // this means you may or may not be logged in, but can't share - // because you are not in a public place. - document.getElementById("sharing").innerHTML = "

Snapshots can be shared when they're taken in shareable places."; } + } window.onload = function () { // TESTING FUNCTIONS START // Uncomment and modify the lines below to test SnapshotReview in a browser. - imageCount = 2; - addImage({ localPath: 'D:/Dropbox/Screenshots/High Fidelity Snapshots/2017-01-27 50 Avatars!/!!!.gif' }); - addImage({ localPath: 'http://lorempixel.com/553/255' }); + //imageCount = 2; + //addImage({ localPath: 'C:/Users/Zach Fox/Desktop/hifi-snap-by-zfox-on-2017-04-20_14-59-12.gif' }); + //addImage({ localPath: 'C:/Users/Zach Fox/Desktop/hifi-snap-by-zfox-on-2017-04-20_14-59-12.jpg' }); + //addImage({ localPath: 'http://lorempixel.com/553/255' }); //addImage({localPath: 'c:/Users/howar/OneDrive/Pictures/hifi-snap-by--on-2016-07-27_12-58-43.jpg'}); // TESTING FUNCTIONS END @@ -192,36 +177,42 @@ window.onload = function () { message = JSON.parse(message); - switch (message.type) { - case 'snapshot': + if (message.type !== "snapshot") { + return; + } + + switch (message.action) { + case 'addImages': // The last element of the message contents list contains a bunch of options, // including whether or not we can share stuff // The other elements of the list contain image paths. - var messageOptions = message.action.pop(); - handleShareButtons(messageOptions); + var messageOptions = message.options; if (messageOptions.containsGif) { if (messageOptions.processingGif) { - imageCount = message.action.length + 1; // "+1" for the GIF that'll finish processing soon - message.action.unshift({ localPath: messageOptions.loadingGifPath }); - message.action.forEach(addImage); + imageCount = message.data.length + 1; // "+1" for the GIF that'll finish processing soon + message.data.unshift({ localPath: messageOptions.loadingGifPath }); + message.data.forEach(addImage); } else { - var gifPath = message.action[0].localPath; + var gifPath = message.data[0].localPath; document.getElementById('p0img').src = gifPath; paths[0].localPath = gifPath; } } else { - imageCount = message.action.length; - message.action.forEach(addImage); + imageCount = message.data.length; + message.data.forEach(addImage); } break; - case 'snapshotSettings': - handleCaptureSetting(message.action); + case 'captureSettings': + handleCaptureSetting(message.setting); break; default: - return; + print("Unknown message action received in SnapshotReview.js."); + break; } + }); + EventBridge.emitWebEvent(JSON.stringify({ type: "snapshot", action: "ready" diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 266f90c704..97cd2c542e 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -43,7 +43,8 @@ function showFeedWindow() { } var outstanding; -var readyData; +var snapshotOptions; +var imageData; var shareAfterLogin = false; var snapshotToShareAfterLogin; function onMessage(message) { @@ -62,12 +63,15 @@ function onMessage(message) { switch (message.action) { case 'ready': // Send it. tablet.emitScriptEvent(JSON.stringify({ - type: "snapshotSettings", - action: Settings.getValue("alsoTakeAnimatedSnapshot", true) + type: "snapshot", + action: "captureSettings", + setting: Settings.getValue("alsoTakeAnimatedSnapshot", true) })); tablet.emitScriptEvent(JSON.stringify({ type: "snapshot", - action: readyData + action: "addImages", + options: snapshotOptions, + data: imageData })); outstanding = 0; break; @@ -79,20 +83,19 @@ function onMessage(message) { tablet.loadQMLOnTop("TabletGeneralPreferences.qml"); } break; - case 'captureSettings': - Settings.setValue("alsoTakeAnimatedSnapshot", message.action); + case 'captureStillAndGif': + print("Changing Snapshot Capture Settings to Capture Still + GIF"); + Settings.setValue("alsoTakeAnimatedSnapshot", true); + break; + case 'captureStillOnly': + print("Changing Snapshot Capture Settings to Capture Still Only"); + Settings.setValue("alsoTakeAnimatedSnapshot", false); break; case 'takeSnapshot': // In settings, first store the paths to the last snapshot // onClicked(); break; - case 'setOpenFeedFalse': - Settings.setValue('openFeedAfterShare', false); - break; - case 'setOpenFeedTrue': - Settings.setValue('openFeedAfterShare', true); - break; default: //tablet.webEventReceived.disconnect(onMessage); // <<< It's probably this that's missing?! HMD.closeTablet(); @@ -135,9 +138,8 @@ function onMessage(message) { var SNAPSHOT_REVIEW_URL = Script.resolvePath("html/SnapshotReview.html"); var isInSnapshotReview = false; -function confirmShare(data) { +function reviewSnapshot() { tablet.gotoWebScreen(SNAPSHOT_REVIEW_URL); - readyData = data; tablet.webEventReceived.connect(onMessage); HMD.openTablet(); isInSnapshotReview = true; @@ -182,7 +184,7 @@ function onClicked() { Script.setTimeout(function () { HMD.closeTablet(); Script.setTimeout(function () { - Window.takeSnapshot(false, true, 1.91); + Window.takeSnapshot(false, Settings.getValue("alsoTakeAnimatedSnapshot", true), 1.91); }, SNAPSHOT_DELAY); }, FINISH_SOUND_DELAY); } @@ -220,15 +222,14 @@ function stillSnapshotTaken(pathStillSnapshot, notify) { // during which time the user may have moved. So stash that info in the dialog so that // it records the correct href. (We can also stash in .jpegs, but not .gifs.) // last element in data array tells dialog whether we can share or not - var confirmShareContents = [ - { localPath: pathStillSnapshot, href: href }, - { - containsGif: false, - processingGif: false, - canShare: !!isDomainOpen(domainId), - openFeedAfterShare: shouldOpenFeedAfterShare() - }]; - confirmShare(confirmShareContents); + snapshotOptions = { + containsGif: false, + processingGif: false, + canShare: !!isDomainOpen(domainId), + openFeedAfterShare: shouldOpenFeedAfterShare() + }; + imageData = [{ localPath: pathStillSnapshot, href: href }]; + reviewSnapshot(); if (clearOverlayWhenMoving) { MyAvatar.setClearOverlayWhenMoving(true); // not until after the share dialog } @@ -237,8 +238,10 @@ function stillSnapshotTaken(pathStillSnapshot, notify) { function processingGifStarted(pathStillSnapshot) { Window.processingGifStarted.disconnect(processingGifStarted); - button.clicked.disconnect(onClicked); - buttonConnected = false; + if (buttonConnected) { + button.clicked.disconnect(onClicked); + buttonConnected = false; + } // show hud Reticle.visible = reticleVisible; // show overlays if they were on @@ -246,16 +249,15 @@ function processingGifStarted(pathStillSnapshot) { Menu.setIsOptionChecked("Overlays", true); } - var confirmShareContents = [ - { localPath: pathStillSnapshot, href: href }, - { - containsGif: true, - processingGif: true, - loadingGifPath: Script.resolvePath(Script.resourcesPath() + 'icons/loadingDark.gif'), - canShare: !!isDomainOpen(domainId), - openFeedAfterShare: shouldOpenFeedAfterShare() - }]; - confirmShare(confirmShareContents); + snapshotOptions = { + containsGif: true, + processingGif: true, + loadingGifPath: Script.resolvePath(Script.resourcesPath() + 'icons/loadingDark.gif'), + canShare: !!isDomainOpen(domainId), + openFeedAfterShare: shouldOpenFeedAfterShare() + }; + imageData = [{ localPath: pathStillSnapshot, href: href }]; + reviewSnapshot(); if (clearOverlayWhenMoving) { MyAvatar.setClearOverlayWhenMoving(true); // not until after the share dialog } @@ -264,22 +266,24 @@ function processingGifStarted(pathStillSnapshot) { function processingGifCompleted(pathAnimatedSnapshot) { Window.processingGifCompleted.disconnect(processingGifCompleted); - button.clicked.connect(onClicked); - buttonConnected = true; + if (!buttonConnected) { + button.clicked.connect(onClicked); + buttonConnected = true; + } - var confirmShareContents = [ - { localPath: pathAnimatedSnapshot, href: href }, - { - containsGif: true, - processingGif: false, - canShare: !!isDomainOpen(domainId), - openFeedAfterShare: shouldOpenFeedAfterShare() - }]; - readyData = confirmShareContents; + snapshotOptions = { + containsGif: true, + processingGif: false, + canShare: !!isDomainOpen(domainId), + openFeedAfterShare: shouldOpenFeedAfterShare() + } + imageData = [{ localPath: pathAnimatedSnapshot, href: href }]; tablet.emitScriptEvent(JSON.stringify({ type: "snapshot", - action: readyData + action: "addImages", + options: snapshotOptions, + data: imageData })); } From 01e78612c7ece62d22d1bf9d16b583ac287e760d Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 20 Apr 2017 17:17:35 -0700 Subject: [PATCH 49/76] Soooo much progress today --- scripts/system/html/SnapshotReview.html | 9 +++-- scripts/system/html/css/SnapshotReview.css | 10 +++--- scripts/system/html/css/hifi-style.css | 40 ++++++++++++++++++++++ scripts/system/html/js/SnapshotReview.js | 28 ++++++++++----- 4 files changed, 69 insertions(+), 18 deletions(-) diff --git a/scripts/system/html/SnapshotReview.html b/scripts/system/html/SnapshotReview.html index e785fde69d..400be4abdc 100644 --- a/scripts/system/html/SnapshotReview.html +++ b/scripts/system/html/SnapshotReview.html @@ -27,7 +27,7 @@

- +
@@ -40,10 +40,9 @@

- -
- - + +
+
diff --git a/scripts/system/html/css/SnapshotReview.css b/scripts/system/html/css/SnapshotReview.css index 58866feef0..1e233b22f3 100644 --- a/scripts/system/html/css/SnapshotReview.css +++ b/scripts/system/html/css/SnapshotReview.css @@ -20,8 +20,8 @@ body { .title { padding: 6px 10px; text-align: left; - height: 20px; - line-height: 20px; + height: 26px; + line-height: 26px; clear: both; } @@ -43,7 +43,7 @@ body { } .hifi-glyph { font-size: 30px; - top: -7px; + top: -4px; } input[type=button].naked { color: #afafaf; @@ -64,7 +64,7 @@ input[type=button].naked:active { */ #snapshot-pane { width: 100%; - height: 574px; + height: 560px; display: flex; justify-content: center; align-items: center; @@ -170,7 +170,7 @@ input[type=button].naked:active { margin-left: 10px; } #snap-settings form input { - margin-bottom: 10px; + margin-bottom: 5px; } #snap-button { diff --git a/scripts/system/html/css/hifi-style.css b/scripts/system/html/css/hifi-style.css index e95ceca4da..41cda569c9 100644 --- a/scripts/system/html/css/hifi-style.css +++ b/scripts/system/html/css/hifi-style.css @@ -92,3 +92,43 @@ hr { //margin: -10px; padding: 0; } + +input[type=radio] { + width: 2em; + margin: 0; + padding: 0; + font-size: 1em; + opacity: 0; +} +input[type=radio] + label{ + display: inline-block; + margin-left: -2em; + line-height: 2em; +} +input[type=radio] + label > span{ + display: inline-block; + width: 20px; + height: 20px; + margin: 5px; + border-radius: 50%; + background: #6B6A6B; + background-image: linear-gradient(#7D7D7D, #6B6A6B); + vertical-align: bottom; +} +input[type=radio]:checked + label > span{ + background-image: linear-gradient(#7D7D7D, #6B6A6B); +} +input[type=radio]:active + label > span, +input[type=radio]:hover + label > span{ + background-image: linear-gradient(#FFFFFF, #AFAFAF); +} +input[type=radio]:checked + label > span > span, +input[type=radio]:active + label > span > span{ + display: block; + width: 10px; + height: 10px; + margin: 2.5px; + border: 2px solid #36CDFF; + border-radius: 50%; + background: #00B4EF; +} \ No newline at end of file diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index 3346635a6a..aae1fc5787 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -21,7 +21,7 @@ function addImage(data) { div.id = id; img.id = id + "img"; div.style.width = "100%"; - div.style.height = "" + Math.floor(100 / imageCount) + "%"; + div.style.height = "" + 502 / imageCount + "px"; div.style.display = "flex"; div.style.justifyContent = "center"; div.style.alignItems = "center"; @@ -32,13 +32,16 @@ function addImage(data) { img.src = data.localPath; div.appendChild(img); document.getElementById("snapshot-images").appendChild(div); - div.appendChild(createShareOverlay(id, img.src.split('.').pop().toLowerCase() === "gif")); - img.onload = function () { - var shareBar = document.getElementById(id + "shareBar"); - shareBar.style.width = img.clientWidth; - shareBar.style.display = "inline"; + var isGif = img.src.split('.').pop().toLowerCase() === "gif"; + div.appendChild(createShareOverlay(id, isGif)); + if (!isGif) { + img.onload = function () { + var shareBar = document.getElementById(id + "shareBar"); + shareBar.style.width = img.clientWidth; + shareBar.style.display = "inline"; - document.getElementById(id).style.height = img.clientHeight; + document.getElementById(id).style.height = img.clientHeight; + } } paths.push(data); } @@ -195,7 +198,16 @@ window.onload = function () { message.data.forEach(addImage); } else { var gifPath = message.data[0].localPath; - document.getElementById('p0img').src = gifPath; + var p0img = document.getElementById('p0img'); + p0img.src = gifPath; + + p0img.onload = function () { + var shareBar = document.getElementById("p0shareBar"); + shareBar.style.width = p0img.clientWidth; + shareBar.style.display = "inline"; + document.getElementById('p0').style.height = p0img.clientHeight; + } + paths[0].localPath = gifPath; } } else { From c08b40b95009aec68759926e9212084f58450cae Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Fri, 21 Apr 2017 10:17:49 -0700 Subject: [PATCH 50/76] Initial sharing code hooked up --- scripts/system/html/js/SnapshotReview.js | 22 +++++----- scripts/system/snapshot.js | 56 +++++++++--------------- 2 files changed, 32 insertions(+), 46 deletions(-) diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index aae1fc5787..8fffd207ff 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -43,7 +43,7 @@ function addImage(data) { document.getElementById(id).style.height = img.clientHeight; } } - paths.push(data); + paths.push(data.localPath); } function createShareOverlay(parentID, isGif) { var shareOverlayContainer = document.createElement("DIV"); @@ -99,7 +99,7 @@ function createShareOverlay(parentID, isGif) { '
' + '
' + '
' + - '
' + + '
' + '' + '
' + '' + @@ -129,6 +129,15 @@ function selectImageToShare(selectedID) { shareOverlayBackground.style.display = "inline"; shareOverlay.style.display = "inline"; } +function shareWithEveryone(selectedID) { + selectedID = selectedID.id; // Why is this necessary? + + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "shareSnapshot", + data: paths[parseInt(selectedID.substring(1))] + })); +} function cancelSharing(selectedID) { selectedID = selectedID.id; // Why is this necessary? var shareOverlayContainer = document.getElementById(selectedID + "shareOverlayContainer"); @@ -208,7 +217,7 @@ window.onload = function () { document.getElementById('p0').style.height = p0img.clientHeight; } - paths[0].localPath = gifPath; + paths[0] = gifPath; } } else { imageCount = message.data.length; @@ -232,13 +241,6 @@ window.onload = function () { }); }; -// beware of bug: Cannot send objects at top level. (Nested in arrays is fine.) -function shareSelected() { - EventBridge.emitWebEvent(JSON.stringify({ - type: "snapshot", - action: paths - })); -} function doNotShare() { EventBridge.emitWebEvent(JSON.stringify({ type: "snapshot", diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 97cd2c542e..7f71336dcf 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -96,43 +96,31 @@ function onMessage(message) { // onClicked(); break; - default: - //tablet.webEventReceived.disconnect(onMessage); // <<< It's probably this that's missing?! - HMD.closeTablet(); + case 'shareSnapshot': isLoggedIn = Account.isLoggedIn(); - message.action.forEach(function (submessage) { - if (submessage.share && !isLoggedIn) { - needsLogin = true; - submessage.share = false; - shareAfterLogin = true; - snapshotToShareAfterLogin = {path: submessage.localPath, href: submessage.href || href}; - } - if (submessage.share) { - print('sharing', submessage.localPath); - outstanding = true; - Window.shareSnapshot(submessage.localPath, submessage.href || href); - } else { - print('not sharing', submessage.localPath); - } - - }); - if (outstanding && shouldOpenFeedAfterShare()) { - showFeedWindow(); - outstanding = false; + if (!isLoggedIn) { + needsLogin = true; + shareAfterLogin = true; + snapshotToShareAfterLogin = { path: message.data, href: message.href || href }; + } else { + print('sharing', message.data); + outstanding++; + Window.shareSnapshot(message.data, message.href || href); } - if (needsLogin) { // after the possible feed, so that the login is on top - var isLoggedIn = Account.isLoggedIn(); - if (!isLoggedIn) { - if ((HMD.active && Settings.getValue("hmdTabletBecomesToolbar")) - || (!HMD.active && Settings.getValue("desktopTabletBecomesToolbar"))) { - Menu.triggerOption("Login / Sign Up"); - } else { - tablet.loadQMLOnTop("../../dialogs/TabletLoginDialog.qml"); - HMD.openTablet(); - } + if (needsLogin) { + if ((HMD.active && Settings.getValue("hmdTabletBecomesToolbar")) + || (!HMD.active && Settings.getValue("desktopTabletBecomesToolbar"))) { + Menu.triggerOption("Login / Sign Up"); + } else { + tablet.loadQMLOnTop("../../dialogs/TabletLoginDialog.qml"); + HMD.openTablet(); } } + break; + default: + print('Unknown message action received by snapshot.js!'); + break; } } @@ -298,11 +286,7 @@ function onConnected() { print('sharing', snapshotToShareAfterLogin.path); Window.shareSnapshot(snapshotToShareAfterLogin.path, snapshotToShareAfterLogin.href); shareAfterLogin = false; - if (shouldOpenFeedAfterShare()) { - showFeedWindow(); - } } - } button.clicked.connect(onClicked); From 880bcf3b1ee2558c8053d66615d28f232118977c Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Fri, 21 Apr 2017 15:42:41 -0700 Subject: [PATCH 51/76] Checkpoint --- .../src/scripting/WindowScriptingInterface.h | 2 +- interface/src/ui/SnapshotUploader.cpp | 15 +- scripts/system/html/js/SnapshotReview.js | 31 +++- scripts/system/snapshot.js | 138 ++++++++++++------ 4 files changed, 133 insertions(+), 53 deletions(-) diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index eb7dc02627..7641110972 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -74,7 +74,7 @@ signals: void svoImportRequested(const QString& url); void domainConnectionRefused(const QString& reasonMessage, int reasonCode, const QString& extraInfo); void stillSnapshotTaken(const QString& pathStillSnapshot, bool notify); - void snapshotShared(const QString& error); + void snapshotShared(bool isError, const QString& reply); void processingGifStarted(const QString& pathStillSnapshot); void processingGifCompleted(const QString& pathAnimatedSnapshot); diff --git a/interface/src/ui/SnapshotUploader.cpp b/interface/src/ui/SnapshotUploader.cpp index 411e892de5..54faa822b8 100644 --- a/interface/src/ui/SnapshotUploader.cpp +++ b/interface/src/ui/SnapshotUploader.cpp @@ -49,6 +49,7 @@ void SnapshotUploader::uploadSuccess(QNetworkReply& reply) { userStoryObject.insert("place_name", placeName); userStoryObject.insert("path", currentPath); userStoryObject.insert("action", "snapshot"); + userStoryObject.insert("audience", "for_url"); rootObject.insert("user_story", userStoryObject); auto accountManager = DependencyManager::get(); @@ -61,7 +62,7 @@ void SnapshotUploader::uploadSuccess(QNetworkReply& reply) { QJsonDocument(rootObject).toJson()); } else { - emit DependencyManager::get()->snapshotShared(contents); + emit DependencyManager::get()->snapshotShared(true, contents); delete this; } } @@ -72,12 +73,18 @@ void SnapshotUploader::uploadFailure(QNetworkReply& reply) { if (replyString.size() == 0) { replyString = reply.errorString(); } - emit DependencyManager::get()->snapshotShared(replyString); // maybe someday include _inWorldLocation, _filename? + emit DependencyManager::get()->snapshotShared(true, replyString); // maybe someday include _inWorldLocation, _filename? delete this; } void SnapshotUploader::createStorySuccess(QNetworkReply& reply) { - emit DependencyManager::get()->snapshotShared(QString()); + QString replyString = reply.readAll(); + // oh no QT pls + // let's write our own JSON parser??? + QJsonDocument jsonResponse = QJsonDocument::fromJson(replyString.toUtf8()); + QJsonObject object = jsonResponse.object()["user_story"].toObject(); + QString storyId = QString::number(object["id"].toInt()); + emit DependencyManager::get()->snapshotShared(false, storyId); delete this; } @@ -87,7 +94,7 @@ void SnapshotUploader::createStoryFailure(QNetworkReply& reply) { if (replyString.size() == 0) { replyString = reply.errorString(); } - emit DependencyManager::get()->snapshotShared(replyString); + emit DependencyManager::get()->snapshotShared(true, replyString); delete this; } diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index 8fffd207ff..042bc55fb5 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -11,7 +11,7 @@ // var paths = [], idCounter = 0, imageCount = 1; -function addImage(data) { +function addImage(data, isGifLoading) { if (!data.localPath) { return; } @@ -38,12 +38,14 @@ function addImage(data) { img.onload = function () { var shareBar = document.getElementById(id + "shareBar"); shareBar.style.width = img.clientWidth; - shareBar.style.display = "inline"; document.getElementById(id).style.height = img.clientHeight; } } paths.push(data.localPath); + if (!isGifLoading) { + shareForUrl(id); + } } function createShareOverlay(parentID, isGif) { var shareOverlayContainer = document.createElement("DIV"); @@ -99,7 +101,7 @@ function createShareOverlay(parentID, isGif) { '
' + '
' + '
' + - '
' + + '
' + '' + '
' + '' + @@ -129,12 +131,19 @@ function selectImageToShare(selectedID) { shareOverlayBackground.style.display = "inline"; shareOverlay.style.display = "inline"; } +function shareForUrl(selectedID) { + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "shareSnapshotForUrl", + data: paths[parseInt(selectedID.substring(1))] + })); +} function shareWithEveryone(selectedID) { selectedID = selectedID.id; // Why is this necessary? EventBridge.emitWebEvent(JSON.stringify({ type: "snapshot", - action: "shareSnapshot", + action: "shareSnapshotWithEveryone", data: paths[parseInt(selectedID.substring(1))] })); } @@ -204,7 +213,9 @@ window.onload = function () { if (messageOptions.processingGif) { imageCount = message.data.length + 1; // "+1" for the GIF that'll finish processing soon message.data.unshift({ localPath: messageOptions.loadingGifPath }); - message.data.forEach(addImage); + message.data.forEach(function (element, idx, array) { + addImage(element, idx === 0); + }); } else { var gifPath = message.data[0].localPath; var p0img = document.getElementById('p0img'); @@ -213,20 +224,26 @@ window.onload = function () { p0img.onload = function () { var shareBar = document.getElementById("p0shareBar"); shareBar.style.width = p0img.clientWidth; - shareBar.style.display = "inline"; document.getElementById('p0').style.height = p0img.clientHeight; } paths[0] = gifPath; + shareForUrl("p0"); } } else { imageCount = message.data.length; - message.data.forEach(addImage); + message.data.forEach(function (element, idx, array) { + addImage(element, false); + }); } break; case 'captureSettings': handleCaptureSetting(message.setting); break; + case 'enableShareButtons': + var shareBar = document.getElementById("p0shareBar"); + shareBar.style.display = "inline"; + break; default: print("Unknown message action received in SnapshotReview.js."); break; diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 7f71336dcf..37f3070dc0 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -28,25 +28,61 @@ var button = tablet.addButton({ sortOrder: 5 }); -function shouldOpenFeedAfterShare() { - var persisted = Settings.getValue('openFeedAfterShare', true); // might answer true, false, "true", or "false" - return persisted && (persisted !== 'false'); -} -function showFeedWindow() { - if ((HMD.active && Settings.getValue("hmdTabletBecomesToolbar")) - || (!HMD.active && Settings.getValue("desktopTabletBecomesToolbar"))) { - tablet.loadQMLSource("TabletAddressDialog.qml"); - } else { - tablet.initialScreen("TabletAddressDialog.qml"); - HMD.openTablet(); - } -} - -var outstanding; var snapshotOptions; var imageData; var shareAfterLogin = false; -var snapshotToShareAfterLogin; +var snapshotToShareAfterLogin; +var METAVERSE_BASE = location.metaverseServerUrl; + +function request(options, callback) { // cb(error, responseOfCorrectContentType) of url. A subset of npm request. + var httpRequest = new XMLHttpRequest(), key; + // QT bug: apparently doesn't handle onload. Workaround using readyState. + httpRequest.onreadystatechange = function () { + var READY_STATE_DONE = 4; + var HTTP_OK = 200; + if (httpRequest.readyState >= READY_STATE_DONE) { + var error = (httpRequest.status !== HTTP_OK) && httpRequest.status.toString() + ':' + httpRequest.statusText, + response = !error && httpRequest.responseText, + contentType = !error && httpRequest.getResponseHeader('content-type'); + if (!error && contentType.indexOf('application/json') === 0) { // ignoring charset, etc. + try { + response = JSON.parse(response); + } catch (e) { + error = e; + } + } + callback(error, response); + } + }; + if (typeof options === 'string') { + options = { uri: options }; + } + if (options.url) { + options.uri = options.url; + } + if (!options.method) { + options.method = 'GET'; + } + if (options.body && (options.method === 'GET')) { // add query parameters + var params = [], appender = (-1 === options.uri.search('?')) ? '?' : '&'; + for (key in options.body) { + params.push(key + '=' + options.body[key]); + } + options.uri += appender + params.join('&'); + delete options.body; + } + if (options.json) { + options.headers = options.headers || {}; + options.headers["Content-type"] = "application/json"; + options.body = JSON.stringify(options.body); + } + for (key in options.headers || {}) { + httpRequest.setRequestHeader(key, options.headers[key]); + } + httpRequest.open(options.method, options.uri, true); + httpRequest.send(options.body); +} + function onMessage(message) { // Receives message from the html dialog via the qwebchannel EventBridge. This is complicated by the following: // 1. Although we can send POJOs, we cannot receive a toplevel object. (Arrays of POJOs are fine, though.) @@ -73,7 +109,6 @@ function onMessage(message) { options: snapshotOptions, data: imageData })); - outstanding = 0; break; case 'openSettings': if ((HMD.active && Settings.getValue("hmdTabletBecomesToolbar")) @@ -96,18 +131,41 @@ function onMessage(message) { // onClicked(); break; - case 'shareSnapshot': + case 'shareSnapshotForUrl': isLoggedIn = Account.isLoggedIn(); - if (!isLoggedIn) { + if (isLoggedIn) { + print('Sharing snapshot with audience "for_url":', message.data); + Window.shareSnapshot(message.data, message.href || href); + } else { + // TODO? + } + break; + case 'shareSnapshotWithEveryone': + isLoggedIn = Account.isLoggedIn(); + if (isLoggedIn) { + print('sharing', message.data); + request({ + uri: METAVERSE_BASE + '/api/v1/user_stories/' + story_id, + method: 'PUT', + json: true, + body: { + audience: "for_feed", + } + }, function (error, response) { + if (error || (response.status !== 'success')) { + print("Error changing audience: ", error || response.status); + return; + } + }); + } else { + // TODO + /* needsLogin = true; shareAfterLogin = true; snapshotToShareAfterLogin = { path: message.data, href: message.href || href }; - } else { - print('sharing', message.data); - outstanding++; - Window.shareSnapshot(message.data, message.href || href); + */ } - + /* if (needsLogin) { if ((HMD.active && Settings.getValue("hmdTabletBecomesToolbar")) || (!HMD.active && Settings.getValue("desktopTabletBecomesToolbar"))) { @@ -116,7 +174,7 @@ function onMessage(message) { tablet.loadQMLOnTop("../../dialogs/TabletLoginDialog.qml"); HMD.openTablet(); } - } + }*/ break; default: print('Unknown message action received by snapshot.js!'); @@ -133,14 +191,15 @@ function reviewSnapshot() { isInSnapshotReview = true; } -function snapshotShared(errorMessage) { - if (!errorMessage) { - print('snapshot uploaded and shared'); +function snapshotUploaded(isError, reply) { + if (!isError) { + print('SUCCESS: Snapshot uploaded! Story with audience:for_url created! ID:', reply); + tablet.emitScriptEvent(JSON.stringify({ + type: "snapshot", + action: "enableShareButtons" + })); } else { - print(errorMessage); - } - if ((--outstanding <= 0) && shouldOpenFeedAfterShare()) { - showFeedWindow(); + print(reply); } } var href, domainId; @@ -213,8 +272,7 @@ function stillSnapshotTaken(pathStillSnapshot, notify) { snapshotOptions = { containsGif: false, processingGif: false, - canShare: !!isDomainOpen(domainId), - openFeedAfterShare: shouldOpenFeedAfterShare() + canShare: !!isDomainOpen(domainId) }; imageData = [{ localPath: pathStillSnapshot, href: href }]; reviewSnapshot(); @@ -241,8 +299,7 @@ function processingGifStarted(pathStillSnapshot) { containsGif: true, processingGif: true, loadingGifPath: Script.resolvePath(Script.resourcesPath() + 'icons/loadingDark.gif'), - canShare: !!isDomainOpen(domainId), - openFeedAfterShare: shouldOpenFeedAfterShare() + canShare: !!isDomainOpen(domainId) }; imageData = [{ localPath: pathStillSnapshot, href: href }]; reviewSnapshot(); @@ -262,8 +319,7 @@ function processingGifCompleted(pathAnimatedSnapshot) { snapshotOptions = { containsGif: true, processingGif: false, - canShare: !!isDomainOpen(domainId), - openFeedAfterShare: shouldOpenFeedAfterShare() + canShare: !!isDomainOpen(domainId) } imageData = [{ localPath: pathAnimatedSnapshot, href: href }]; @@ -283,7 +339,7 @@ function onTabletScreenChanged(type, url) { } function onConnected() { if (shareAfterLogin && Account.isLoggedIn()) { - print('sharing', snapshotToShareAfterLogin.path); + print('Sharing snapshot after login:', snapshotToShareAfterLogin.path); Window.shareSnapshot(snapshotToShareAfterLogin.path, snapshotToShareAfterLogin.href); shareAfterLogin = false; } @@ -291,7 +347,7 @@ function onConnected() { button.clicked.connect(onClicked); buttonConnected = true; -Window.snapshotShared.connect(snapshotShared); +Window.snapshotShared.connect(snapshotUploaded); tablet.screenChanged.connect(onTabletScreenChanged); Account.usernameChanged.connect(onConnected); Script.scriptEnding.connect(function () { @@ -302,7 +358,7 @@ Script.scriptEnding.connect(function () { if (tablet) { tablet.removeButton(button); } - Window.snapshotShared.disconnect(snapshotShared); + Window.snapshotShared.disconnect(snapshotUploaded); tablet.screenChanged.disconnect(onTabletScreenChanged); }); From eaa699bbfdba3fe875bfe78b1cf5ab2f6147c083 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Fri, 21 Apr 2017 17:09:14 -0700 Subject: [PATCH 52/76] Wow, it's really coming together! --- interface/src/ui/SnapshotUploader.cpp | 7 +---- scripts/system/html/js/SnapshotReview.js | 37 +++++++++--------------- scripts/system/snapshot.js | 16 ++++++---- 3 files changed, 26 insertions(+), 34 deletions(-) diff --git a/interface/src/ui/SnapshotUploader.cpp b/interface/src/ui/SnapshotUploader.cpp index 54faa822b8..aa37608476 100644 --- a/interface/src/ui/SnapshotUploader.cpp +++ b/interface/src/ui/SnapshotUploader.cpp @@ -79,12 +79,7 @@ void SnapshotUploader::uploadFailure(QNetworkReply& reply) { void SnapshotUploader::createStorySuccess(QNetworkReply& reply) { QString replyString = reply.readAll(); - // oh no QT pls - // let's write our own JSON parser??? - QJsonDocument jsonResponse = QJsonDocument::fromJson(replyString.toUtf8()); - QJsonObject object = jsonResponse.object()["user_story"].toObject(); - QString storyId = QString::number(object["id"].toInt()); - emit DependencyManager::get()->snapshotShared(false, storyId); + emit DependencyManager::get()->snapshotShared(false, replyString); delete this; } diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index 042bc55fb5..6e15dad567 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -33,21 +33,12 @@ function addImage(data, isGifLoading) { div.appendChild(img); document.getElementById("snapshot-images").appendChild(div); var isGif = img.src.split('.').pop().toLowerCase() === "gif"; - div.appendChild(createShareOverlay(id, isGif)); - if (!isGif) { - img.onload = function () { - var shareBar = document.getElementById(id + "shareBar"); - shareBar.style.width = img.clientWidth; - - document.getElementById(id).style.height = img.clientHeight; - } - } paths.push(data.localPath); if (!isGifLoading) { shareForUrl(id); } } -function createShareOverlay(parentID, isGif) { +function createShareOverlay(parentID, isGif, shareURL) { var shareOverlayContainer = document.createElement("DIV"); shareOverlayContainer.id = parentID + "shareOverlayContainer"; shareOverlayContainer.style.position = "absolute"; @@ -60,7 +51,7 @@ function createShareOverlay(parentID, isGif) { var shareBar = document.createElement("div"); shareBar.id = parentID + "shareBar" - shareBar.style.display = "none"; + shareBar.style.display = "inline"; shareBar.style.width = "100%"; shareBar.style.height = "60px"; shareBar.style.lineHeight = "60px"; @@ -107,8 +98,8 @@ function createShareOverlay(parentID, isGif) { '' + '
' + '
' + - '' + - '' + + '' + + '' + '
' + '
'; shareOverlayContainer.appendChild(shareOverlay); @@ -144,7 +135,7 @@ function shareWithEveryone(selectedID) { EventBridge.emitWebEvent(JSON.stringify({ type: "snapshot", action: "shareSnapshotWithEveryone", - data: paths[parseInt(selectedID.substring(1))] + story_id: document.getElementById(selectedID).getAttribute("data-story-id") })); } function cancelSharing(selectedID) { @@ -221,12 +212,6 @@ window.onload = function () { var p0img = document.getElementById('p0img'); p0img.src = gifPath; - p0img.onload = function () { - var shareBar = document.getElementById("p0shareBar"); - shareBar.style.width = p0img.clientWidth; - document.getElementById('p0').style.height = p0img.clientHeight; - } - paths[0] = gifPath; shareForUrl("p0"); } @@ -240,9 +225,15 @@ window.onload = function () { case 'captureSettings': handleCaptureSetting(message.setting); break; - case 'enableShareButtons': - var shareBar = document.getElementById("p0shareBar"); - shareBar.style.display = "inline"; + case 'snapshotUploadComplete': + var isGif = message.shareable_url.split('.').pop().toLowerCase() === "gif"; + var id = "p0" + if (imageCount > 1 && !isGif) { + id = "p1"; + } + var parentDiv = document.getElementById(id); + parentDiv.setAttribute('data-story-id', message.id); + document.getElementById(id).appendChild(createShareOverlay(id, isGif, message.story_url)); break; default: print("Unknown message action received in SnapshotReview.js."); diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 37f3070dc0..6b0b8e1214 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -143,9 +143,9 @@ function onMessage(message) { case 'shareSnapshotWithEveryone': isLoggedIn = Account.isLoggedIn(); if (isLoggedIn) { - print('sharing', message.data); + print('Modifying audience of story ID', message.story_id, "to 'for_feed'"); request({ - uri: METAVERSE_BASE + '/api/v1/user_stories/' + story_id, + uri: METAVERSE_BASE + '/api/v1/user_stories/' + message.story_id, method: 'PUT', json: true, body: { @@ -153,8 +153,10 @@ function onMessage(message) { } }, function (error, response) { if (error || (response.status !== 'success')) { - print("Error changing audience: ", error || response.status); + print("ERROR changing audience: ", error || response.status); return; + } else { + print("SUCCESS changing audience!"); } }); } else { @@ -193,10 +195,14 @@ function reviewSnapshot() { function snapshotUploaded(isError, reply) { if (!isError) { - print('SUCCESS: Snapshot uploaded! Story with audience:for_url created! ID:', reply); + print('SUCCESS: Snapshot uploaded! Story with audience:for_url created!'); + var replyJson = JSON.parse(reply); tablet.emitScriptEvent(JSON.stringify({ type: "snapshot", - action: "enableShareButtons" + action: "snapshotUploadComplete", + id: replyJson.user_story.id, + story_url: "https://highfidelity.com/user_stories/" + replyJson.user_story.id, + shareable_url: replyJson.user_story.details.shareable_url, })); } else { print(reply); From fad470eeea20dd9609f62764a99241f073648854 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Mon, 24 Apr 2017 14:35:05 -0700 Subject: [PATCH 53/76] Checkpoint (this is complicated) --- scripts/system/html/js/SnapshotReview.js | 86 +++++++++++------ scripts/system/snapshot.js | 112 ++++++++++++++++++----- 2 files changed, 148 insertions(+), 50 deletions(-) diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index 6e15dad567..cf1f881a46 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -10,9 +10,20 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -var paths = [], idCounter = 0, imageCount = 1; -function addImage(data, isGifLoading) { - if (!data.localPath) { +var paths = []; +var idCounter = 0; +var imageCount = 0; +function clearImages() { + var snapshotImagesDiv = document.getElementById("snapshot-images"); + while (snapshotImagesDiv.hasChildNodes()) { + snapshotImagesDiv.removeChild(snapshotImagesDiv.lastChild); + } + paths = []; + imageCount = 0; + idCounter = 0; +} +function addImage(image_data, isGifLoading, isShowingPreviousImages) { + if (!image_data.localPath) { return; } var div = document.createElement("DIV"); @@ -29,15 +40,26 @@ function addImage(data, isGifLoading) { if (imageCount > 1) { img.setAttribute("class", "multiple"); } - img.src = data.localPath; + img.src = image_data.localPath; div.appendChild(img); document.getElementById("snapshot-images").appendChild(div); var isGif = img.src.split('.').pop().toLowerCase() === "gif"; - paths.push(data.localPath); - if (!isGifLoading) { + paths.push(image_data.localPath); + if (!isGifLoading && !isShowingPreviousImages) { shareForUrl(id); + } else if (isShowingPreviousImages) { + appendShareBar(id, image_data.story_id, isGif) } } +function appendShareBar(divID, story_id, isGif) { + var story_url = "https://highfidelity.com/user_stories/" + story_id; + var parentDiv = document.getElementById(divID); + parentDiv.setAttribute('data-story-id', story_id); + document.getElementById(divID).appendChild(createShareOverlay(divID, isGif, story_url)); + twttr.events.bind('click', function (event) { + shareButtonClicked(divID); + }); +} function createShareOverlay(parentID, isGif, shareURL) { var shareOverlayContainer = document.createElement("DIV"); shareOverlayContainer.id = parentID + "shareOverlayContainer"; @@ -138,6 +160,13 @@ function shareWithEveryone(selectedID) { story_id: document.getElementById(selectedID).getAttribute("data-story-id") })); } +function shareButtonClicked(selectedID) { + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "shareButtonClicked", + story_id: document.getElementById(selectedID).getAttribute("data-story-id") + })); +} function cancelSharing(selectedID) { selectedID = selectedID.id; // Why is this necessary? var shareOverlayContainer = document.getElementById(selectedID + "shareOverlayContainer"); @@ -176,9 +205,10 @@ function handleCaptureSetting(setting) { window.onload = function () { // TESTING FUNCTIONS START // Uncomment and modify the lines below to test SnapshotReview in a browser. - //imageCount = 2; + //imageCount = 1; //addImage({ localPath: 'C:/Users/Zach Fox/Desktop/hifi-snap-by-zfox-on-2017-04-20_14-59-12.gif' }); - //addImage({ localPath: 'C:/Users/Zach Fox/Desktop/hifi-snap-by-zfox-on-2017-04-20_14-59-12.jpg' }); + //addImage({ localPath: 'C:/Users/Zach Fox/Desktop/hifi-snap-by-zfox-on-2017-04-24_10-49-20.jpg' }); + //document.getElementById('p0').appendChild(createShareOverlay('p0', false, '')); //addImage({ localPath: 'http://lorempixel.com/553/255' }); //addImage({localPath: 'c:/Users/howar/OneDrive/Pictures/hifi-snap-by--on-2016-07-27_12-58-43.jpg'}); // TESTING FUNCTIONS END @@ -194,6 +224,17 @@ window.onload = function () { } switch (message.action) { + case 'clearPreviousImages': + clearImages(); + break; + case 'showPreviousImages': + clearImages(); + var messageOptions = message.options; + imageCount = message.image_data.length; + message.image_data.forEach(function (element, idx, array) { + addImage(element, true, true); + }); + break; case 'addImages': // The last element of the message contents list contains a bunch of options, // including whether or not we can share stuff @@ -202,13 +243,13 @@ window.onload = function () { if (messageOptions.containsGif) { if (messageOptions.processingGif) { - imageCount = message.data.length + 1; // "+1" for the GIF that'll finish processing soon - message.data.unshift({ localPath: messageOptions.loadingGifPath }); - message.data.forEach(function (element, idx, array) { + imageCount = message.image_data.length + 1; // "+1" for the GIF that'll finish processing soon + message.image_data.unshift({ localPath: messageOptions.loadingGifPath }); + message.image_data.forEach(function (element, idx, array) { addImage(element, idx === 0); }); } else { - var gifPath = message.data[0].localPath; + var gifPath = message.image_data[0].localPath; var p0img = document.getElementById('p0img'); p0img.src = gifPath; @@ -216,8 +257,8 @@ window.onload = function () { shareForUrl("p0"); } } else { - imageCount = message.data.length; - message.data.forEach(function (element, idx, array) { + imageCount = message.image_data.length; + message.image_data.forEach(function (element, idx, array) { addImage(element, false); }); } @@ -227,19 +268,12 @@ window.onload = function () { break; case 'snapshotUploadComplete': var isGif = message.shareable_url.split('.').pop().toLowerCase() === "gif"; - var id = "p0" - if (imageCount > 1 && !isGif) { - id = "p1"; - } - var parentDiv = document.getElementById(id); - parentDiv.setAttribute('data-story-id', message.id); - document.getElementById(id).appendChild(createShareOverlay(id, isGif, message.story_url)); + appendShareBar(isGif || imageCount === 1 ? "p0" : "p1", message.story_id, isGif); break; default: - print("Unknown message action received in SnapshotReview.js."); + console.log("Unknown message action received in SnapshotReview.js."); break; } - }); EventBridge.emitWebEvent(JSON.stringify({ @@ -249,12 +283,6 @@ window.onload = function () { }); }; -function doNotShare() { - EventBridge.emitWebEvent(JSON.stringify({ - type: "snapshot", - action: [] - })); -} function snapshotSettings() { EventBridge.emitWebEvent(JSON.stringify({ type: "snapshot", diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 6b0b8e1214..1af35f4061 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -29,7 +29,8 @@ var button = tablet.addButton({ }); var snapshotOptions; -var imageData; +var imageData = []; +var storyIDsToMaybeDelete = []; var shareAfterLogin = false; var snapshotToShareAfterLogin; var METAVERSE_BASE = location.metaverseServerUrl; @@ -105,9 +106,9 @@ function onMessage(message) { })); tablet.emitScriptEvent(JSON.stringify({ type: "snapshot", - action: "addImages", + action: "showPreviousImages", options: snapshotOptions, - data: imageData + image_data: imageData })); break; case 'openSettings': @@ -127,9 +128,7 @@ function onMessage(message) { Settings.setValue("alsoTakeAnimatedSnapshot", false); break; case 'takeSnapshot': - // In settings, first store the paths to the last snapshot - // - onClicked(); + takeSnapshot(); break; case 'shareSnapshotForUrl': isLoggedIn = Account.isLoggedIn(); @@ -142,6 +141,7 @@ function onMessage(message) { break; case 'shareSnapshotWithEveryone': isLoggedIn = Account.isLoggedIn(); + storyIDsToMaybeDelete.splice(storyIDsToMaybeDelete.indexOf(message.story_id), 1); if (isLoggedIn) { print('Modifying audience of story ID', message.story_id, "to 'for_feed'"); request({ @@ -178,6 +178,10 @@ function onMessage(message) { } }*/ break; + case 'shareButtonClicked': + print('Twitter or FB "Share" button clicked! Removing ID', message.story_id, 'from storyIDsToMaybeDelete[].') + storyIDsToMaybeDelete.splice(storyIDsToMaybeDelete.indexOf(message.story_id), 1); + break; default: print('Unknown message action received by snapshot.js!'); break; @@ -186,7 +190,23 @@ function onMessage(message) { var SNAPSHOT_REVIEW_URL = Script.resolvePath("html/SnapshotReview.html"); var isInSnapshotReview = false; -function reviewSnapshot() { +function openSnapApp() { + var previousStillSnapPath = Settings.getValue("previousStillSnapPath"); + var previousStillSnapStoryID = Settings.getValue("previousStillSnapStoryID"); + var previousAnimatedSnapPath = Settings.getValue("previousAnimatedSnapPath"); + var previousAnimatedSnapStoryID = Settings.getValue("previousAnimatedSnapStoryID"); + snapshotOptions = { + containsGif: previousAnimatedSnapPath !== "", + processingGif: false, + shouldUpload: false + } + imageData = []; + if (previousAnimatedSnapPath !== "") { + imageData.push({ localPath: previousAnimatedSnapPath, story_id: previousAnimatedSnapStoryID }); + } + if (previousStillSnapPath !== "") { + imageData.push({ localPath: previousStillSnapPath, story_id: previousStillSnapStoryID }); + } tablet.gotoWebScreen(SNAPSHOT_REVIEW_URL); tablet.webEventReceived.connect(onMessage); HMD.openTablet(); @@ -195,21 +215,28 @@ function reviewSnapshot() { function snapshotUploaded(isError, reply) { if (!isError) { - print('SUCCESS: Snapshot uploaded! Story with audience:for_url created!'); var replyJson = JSON.parse(reply); + var storyID = replyJson.user_story.id; + var shareableURL = replyJson.user_story.details.shareable_url; + var isGif = shareableURL.split('.').pop().toLowerCase() === "gif"; + print('SUCCESS: Snapshot uploaded! Story with audience:for_url created! ID:', storyID); tablet.emitScriptEvent(JSON.stringify({ type: "snapshot", action: "snapshotUploadComplete", - id: replyJson.user_story.id, - story_url: "https://highfidelity.com/user_stories/" + replyJson.user_story.id, - shareable_url: replyJson.user_story.details.shareable_url, + story_id: storyID, + shareable_url: shareableURL, })); + if (isGif) { + Settings.setValue("previousAnimatedSnapStoryID", storyID); + } else { + Settings.setValue("previousStillSnapStoryID", storyID); + } } else { print(reply); } } var href, domainId; -function onClicked() { +function takeSnapshot() { // Raising the desktop for the share dialog at end will interact badly with clearOverlayWhenMoving. // Turn it off now, before we start futzing with things (and possibly moving). clearOverlayWhenMoving = MyAvatar.getClearOverlayWhenMoving(); // Do not use Settings. MyAvatar keeps a separate copy. @@ -220,8 +247,16 @@ function onClicked() { href = location.href; domainId = location.domainId; + tablet.emitScriptEvent(JSON.stringify({ + type: "snapshot", + action: "clearPreviousImages" + })); + maybeDeleteSnapshotStories(); + Settings.setValue("previousStillSnapPath", ""); + Settings.setValue("previousAnimatedSnapPath", ""); + // update button states - resetOverlays = Menu.isOptionChecked("Overlays"); // For completness. Certainly true if the button is visible to be clicke. + resetOverlays = Menu.isOptionChecked("Overlays"); // For completness. Certainly true if the button is visible to be clicked. reticleVisible = Reticle.visible; Reticle.visible = false; Window.stillSnapshotTaken.connect(stillSnapshotTaken); @@ -281,7 +316,15 @@ function stillSnapshotTaken(pathStillSnapshot, notify) { canShare: !!isDomainOpen(domainId) }; imageData = [{ localPath: pathStillSnapshot, href: href }]; - reviewSnapshot(); + Settings.setValue("previousStillSnapPath", pathStillSnapshot); + + tablet.emitScriptEvent(JSON.stringify({ + type: "snapshot", + action: "addImages", + options: snapshotOptions, + image_data: imageData + })); + if (clearOverlayWhenMoving) { MyAvatar.setClearOverlayWhenMoving(true); // not until after the share dialog } @@ -291,7 +334,7 @@ function stillSnapshotTaken(pathStillSnapshot, notify) { function processingGifStarted(pathStillSnapshot) { Window.processingGifStarted.disconnect(processingGifStarted); if (buttonConnected) { - button.clicked.disconnect(onClicked); + button.clicked.disconnect(openSnapApp); buttonConnected = false; } // show hud @@ -308,7 +351,15 @@ function processingGifStarted(pathStillSnapshot) { canShare: !!isDomainOpen(domainId) }; imageData = [{ localPath: pathStillSnapshot, href: href }]; - reviewSnapshot(); + Settings.setValue("previousStillSnapPath", pathStillSnapshot); + + tablet.emitScriptEvent(JSON.stringify({ + type: "snapshot", + action: "addImages", + options: snapshotOptions, + image_data: imageData + })); + if (clearOverlayWhenMoving) { MyAvatar.setClearOverlayWhenMoving(true); // not until after the share dialog } @@ -318,7 +369,7 @@ function processingGifStarted(pathStillSnapshot) { function processingGifCompleted(pathAnimatedSnapshot) { Window.processingGifCompleted.disconnect(processingGifCompleted); if (!buttonConnected) { - button.clicked.connect(onClicked); + button.clicked.connect(openSnapApp); buttonConnected = true; } @@ -328,15 +379,34 @@ function processingGifCompleted(pathAnimatedSnapshot) { canShare: !!isDomainOpen(domainId) } imageData = [{ localPath: pathAnimatedSnapshot, href: href }]; + Settings.setValue("previousAnimatedSnapPath", pathAnimatedSnapshot); tablet.emitScriptEvent(JSON.stringify({ type: "snapshot", action: "addImages", options: snapshotOptions, - data: imageData + image_data: imageData })); } - +function maybeDeleteSnapshotStories() { + if (storyIDsToMaybeDelete.length > 0) { + print("User took new snapshot & didn't share old one(s); deleting old snapshot stories"); + storyIDsToMaybeDelete.forEach(function (element, idx, array) { + request({ + uri: METAVERSE_BASE + '/api/v1/user_stories/' + element, + method: 'DELETE' + }, function (error, response) { + if (error || (response.status !== 'success')) { + print("ERROR deleting snapshot story: ", error || response.status); + return; + } else { + print("SUCCESS deleting snapshot story with ID", element); + } + }) + }); + storyIDsToMaybeDelete = []; + } +} function onTabletScreenChanged(type, url) { if (isInSnapshotReview) { tablet.webEventReceived.disconnect(onMessage); @@ -351,14 +421,14 @@ function onConnected() { } } -button.clicked.connect(onClicked); +button.clicked.connect(openSnapApp); buttonConnected = true; Window.snapshotShared.connect(snapshotUploaded); tablet.screenChanged.connect(onTabletScreenChanged); Account.usernameChanged.connect(onConnected); Script.scriptEnding.connect(function () { if (buttonConnected) { - button.clicked.disconnect(onClicked); + button.clicked.disconnect(openSnapApp); buttonConnected = false; } if (tablet) { From 7c5de1e60b3dca11aadb56cfed6a44388b9e997d Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Mon, 24 Apr 2017 15:19:59 -0700 Subject: [PATCH 54/76] Getting closer --- scripts/system/html/js/SnapshotReview.js | 11 ++++++----- scripts/system/snapshot.js | 22 ++++++++++++++++------ 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index cf1f881a46..d5cc9faf74 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -18,11 +18,12 @@ function clearImages() { while (snapshotImagesDiv.hasChildNodes()) { snapshotImagesDiv.removeChild(snapshotImagesDiv.lastChild); } + twttr.events.unbind('click'); paths = []; imageCount = 0; idCounter = 0; } -function addImage(image_data, isGifLoading, isShowingPreviousImages) { +function addImage(image_data, isGifLoading, canSharePreviousImages) { if (!image_data.localPath) { return; } @@ -45,9 +46,9 @@ function addImage(image_data, isGifLoading, isShowingPreviousImages) { document.getElementById("snapshot-images").appendChild(div); var isGif = img.src.split('.').pop().toLowerCase() === "gif"; paths.push(image_data.localPath); - if (!isGifLoading && !isShowingPreviousImages) { + if (!isGifLoading && !canSharePreviousImages) { shareForUrl(id); - } else if (isShowingPreviousImages) { + } else if (canSharePreviousImages) { appendShareBar(id, image_data.story_id, isGif) } } @@ -232,7 +233,7 @@ window.onload = function () { var messageOptions = message.options; imageCount = message.image_data.length; message.image_data.forEach(function (element, idx, array) { - addImage(element, true, true); + addImage(element, true, message.canShare); }); break; case 'addImages': @@ -267,7 +268,7 @@ window.onload = function () { handleCaptureSetting(message.setting); break; case 'snapshotUploadComplete': - var isGif = message.shareable_url.split('.').pop().toLowerCase() === "gif"; + var isGif = message.image_url.split('.').pop().toLowerCase() === "gif"; appendShareBar(isGif || imageCount === 1 ? "p0" : "p1", message.story_id, isGif); break; default: diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 1af35f4061..39fc45556e 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -108,7 +108,8 @@ function onMessage(message) { type: "snapshot", action: "showPreviousImages", options: snapshotOptions, - image_data: imageData + image_data: imageData, + canShare: !!isDomainOpen(Settings.getValue("previousSnapshotDomainID")) })); break; case 'openSettings': @@ -179,8 +180,9 @@ function onMessage(message) { }*/ break; case 'shareButtonClicked': - print('Twitter or FB "Share" button clicked! Removing ID', message.story_id, 'from storyIDsToMaybeDelete[].') + print('Twitter or FB "Share" button clicked! Removing ID', message.story_id, 'from storyIDsToMaybeDelete[].'); storyIDsToMaybeDelete.splice(storyIDsToMaybeDelete.indexOf(message.story_id), 1); + print('storyIDsToMaybeDelete[] now:', JSON.stringify(storyIDsToMaybeDelete)); break; default: print('Unknown message action received by snapshot.js!'); @@ -217,14 +219,15 @@ function snapshotUploaded(isError, reply) { if (!isError) { var replyJson = JSON.parse(reply); var storyID = replyJson.user_story.id; - var shareableURL = replyJson.user_story.details.shareable_url; - var isGif = shareableURL.split('.').pop().toLowerCase() === "gif"; + storyIDsToMaybeDelete.push(storyID); + var imageURL = replyJson.user_story.details.image_url; + var isGif = imageURL.split('.').pop().toLowerCase() === "gif"; print('SUCCESS: Snapshot uploaded! Story with audience:for_url created! ID:', storyID); tablet.emitScriptEvent(JSON.stringify({ type: "snapshot", action: "snapshotUploadComplete", story_id: storyID, - shareable_url: shareableURL, + image_url: imageURL, })); if (isGif) { Settings.setValue("previousAnimatedSnapStoryID", storyID); @@ -246,14 +249,17 @@ function takeSnapshot() { // Even the domainId could change (e.g., if the user falls into a teleporter while recording). href = location.href; domainId = location.domainId; + Settings.setValue("previousSnapshotDomainID", domainId); + maybeDeleteSnapshotStories(); tablet.emitScriptEvent(JSON.stringify({ type: "snapshot", action: "clearPreviousImages" })); - maybeDeleteSnapshotStories(); Settings.setValue("previousStillSnapPath", ""); + Settings.setValue("previousStillSnapStoryID", ""); Settings.setValue("previousAnimatedSnapPath", ""); + Settings.setValue("previousAnimatedSnapStoryID", ""); // update button states resetOverlays = Menu.isOptionChecked("Overlays"); // For completness. Certainly true if the button is visible to be clicked. @@ -278,6 +284,10 @@ function takeSnapshot() { } function isDomainOpen(id) { + print("Checking open status of domain with ID:", id); + if (!id) { + return false; + } var request = new XMLHttpRequest(); var options = [ 'now=' + new Date().toISOString(), From f838e87f4891627ca64637395148964174a301fd Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Mon, 24 Apr 2017 15:59:19 -0700 Subject: [PATCH 55/76] Pretty stable... --- scripts/system/html/css/SnapshotReview.css | 1 + scripts/system/html/js/SnapshotReview.js | 12 +++--- scripts/system/snapshot.js | 44 +++++++++++++--------- 3 files changed, 34 insertions(+), 23 deletions(-) diff --git a/scripts/system/html/css/SnapshotReview.css b/scripts/system/html/css/SnapshotReview.css index 1e233b22f3..859b4d8c90 100644 --- a/scripts/system/html/css/SnapshotReview.css +++ b/scripts/system/html/css/SnapshotReview.css @@ -11,6 +11,7 @@ body { padding: 0; margin: 0; + overflow: hidden; } /* diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index d5cc9faf74..9fa58e6d33 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -23,7 +23,7 @@ function clearImages() { imageCount = 0; idCounter = 0; } -function addImage(image_data, isGifLoading, canSharePreviousImages) { +function addImage(image_data, isGifLoading, isShowingPreviousImages, canSharePreviousImages) { if (!image_data.localPath) { return; } @@ -46,9 +46,9 @@ function addImage(image_data, isGifLoading, canSharePreviousImages) { document.getElementById("snapshot-images").appendChild(div); var isGif = img.src.split('.').pop().toLowerCase() === "gif"; paths.push(image_data.localPath); - if (!isGifLoading && !canSharePreviousImages) { + if (!isGifLoading && !isShowingPreviousImages) { shareForUrl(id); - } else if (canSharePreviousImages) { + } else if (isShowingPreviousImages && canSharePreviousImages) { appendShareBar(id, image_data.story_id, isGif) } } @@ -233,7 +233,7 @@ window.onload = function () { var messageOptions = message.options; imageCount = message.image_data.length; message.image_data.forEach(function (element, idx, array) { - addImage(element, true, message.canShare); + addImage(element, true, true, message.canShare); }); break; case 'addImages': @@ -247,7 +247,7 @@ window.onload = function () { imageCount = message.image_data.length + 1; // "+1" for the GIF that'll finish processing soon message.image_data.unshift({ localPath: messageOptions.loadingGifPath }); message.image_data.forEach(function (element, idx, array) { - addImage(element, idx === 0); + addImage(element, idx === 0, false, false); }); } else { var gifPath = message.image_data[0].localPath; @@ -260,7 +260,7 @@ window.onload = function () { } else { imageCount = message.image_data.length; message.image_data.forEach(function (element, idx, array) { - addImage(element, false); + addImage(element, false, false, false); }); } break; diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 39fc45556e..422c557391 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -240,6 +240,15 @@ function snapshotUploaded(isError, reply) { } var href, domainId; function takeSnapshot() { + tablet.emitScriptEvent(JSON.stringify({ + type: "snapshot", + action: "clearPreviousImages" + })); + Settings.setValue("previousStillSnapPath", ""); + Settings.setValue("previousStillSnapStoryID", ""); + Settings.setValue("previousAnimatedSnapPath", ""); + Settings.setValue("previousAnimatedSnapStoryID", ""); + // Raising the desktop for the share dialog at end will interact badly with clearOverlayWhenMoving. // Turn it off now, before we start futzing with things (and possibly moving). clearOverlayWhenMoving = MyAvatar.getClearOverlayWhenMoving(); // Do not use Settings. MyAvatar keeps a separate copy. @@ -252,22 +261,22 @@ function takeSnapshot() { Settings.setValue("previousSnapshotDomainID", domainId); maybeDeleteSnapshotStories(); - tablet.emitScriptEvent(JSON.stringify({ - type: "snapshot", - action: "clearPreviousImages" - })); - Settings.setValue("previousStillSnapPath", ""); - Settings.setValue("previousStillSnapStoryID", ""); - Settings.setValue("previousAnimatedSnapPath", ""); - Settings.setValue("previousAnimatedSnapStoryID", ""); // update button states - resetOverlays = Menu.isOptionChecked("Overlays"); // For completness. Certainly true if the button is visible to be clicked. + resetOverlays = Menu.isOptionChecked("Overlays"); // For completeness. Certainly true if the button is visible to be clicked. reticleVisible = Reticle.visible; Reticle.visible = false; - Window.stillSnapshotTaken.connect(stillSnapshotTaken); - Window.processingGifStarted.connect(processingGifStarted); - Window.processingGifCompleted.connect(processingGifCompleted); + + var includeAnimated = Settings.getValue("alsoTakeAnimatedSnapshot", true); + if (includeAnimated) { + Window.processingGifStarted.connect(processingGifStarted); + } else { + Window.stillSnapshotTaken.connect(stillSnapshotTaken); + } + if (buttonConnected) { + button.clicked.disconnect(openSnapApp); + buttonConnected = false; + } // hide overlays if they are on if (resetOverlays) { @@ -278,7 +287,7 @@ function takeSnapshot() { Script.setTimeout(function () { HMD.closeTablet(); Script.setTimeout(function () { - Window.takeSnapshot(false, Settings.getValue("alsoTakeAnimatedSnapshot", true), 1.91); + Window.takeSnapshot(false, includeAnimated, 1.91); }, SNAPSHOT_DELAY); }, FINISH_SOUND_DELAY); } @@ -315,6 +324,10 @@ function stillSnapshotTaken(pathStillSnapshot, notify) { Menu.setIsOptionChecked("Overlays", true); } Window.stillSnapshotTaken.disconnect(stillSnapshotTaken); + if (!buttonConnected) { + button.clicked.connect(openSnapApp); + buttonConnected = true; + } // A Snapshot Review dialog might be left open indefinitely after taking the picture, // during which time the user may have moved. So stash that info in the dialog so that @@ -343,10 +356,7 @@ function stillSnapshotTaken(pathStillSnapshot, notify) { function processingGifStarted(pathStillSnapshot) { Window.processingGifStarted.disconnect(processingGifStarted); - if (buttonConnected) { - button.clicked.disconnect(openSnapApp); - buttonConnected = false; - } + Window.processingGifCompleted.connect(processingGifCompleted); // show hud Reticle.visible = reticleVisible; // show overlays if they were on From e1789996fbe3d69f779ab20d613a2c768079d8eb Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Mon, 24 Apr 2017 16:34:58 -0700 Subject: [PATCH 56/76] Add announcements support --- scripts/system/html/js/SnapshotReview.js | 25 +++++++++++-------- scripts/system/snapshot.js | 31 +++++++++++++++++++----- 2 files changed, 40 insertions(+), 16 deletions(-) diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index 9fa58e6d33..4c5419869d 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -23,7 +23,7 @@ function clearImages() { imageCount = 0; idCounter = 0; } -function addImage(image_data, isGifLoading, isShowingPreviousImages, canSharePreviousImages) { +function addImage(image_data, isGifLoading, isShowingPreviousImages, canSharePreviousImages, hifiShareButtonsDisabled) { if (!image_data.localPath) { return; } @@ -49,19 +49,19 @@ function addImage(image_data, isGifLoading, isShowingPreviousImages, canSharePre if (!isGifLoading && !isShowingPreviousImages) { shareForUrl(id); } else if (isShowingPreviousImages && canSharePreviousImages) { - appendShareBar(id, image_data.story_id, isGif) + appendShareBar(id, image_data.story_id, isGif, hifiShareButtonsDisabled) } } -function appendShareBar(divID, story_id, isGif) { +function appendShareBar(divID, story_id, isGif, hifiShareButtonsDisabled) { var story_url = "https://highfidelity.com/user_stories/" + story_id; var parentDiv = document.getElementById(divID); parentDiv.setAttribute('data-story-id', story_id); - document.getElementById(divID).appendChild(createShareOverlay(divID, isGif, story_url)); + document.getElementById(divID).appendChild(createShareOverlay(divID, isGif, story_url, hifiShareButtonsDisabled)); twttr.events.bind('click', function (event) { shareButtonClicked(divID); }); } -function createShareOverlay(parentID, isGif, shareURL) { +function createShareOverlay(parentID, isGif, shareURL, hifiShareButtonsDisabled) { var shareOverlayContainer = document.createElement("DIV"); shareOverlayContainer.id = parentID + "shareOverlayContainer"; shareOverlayContainer.style.position = "absolute"; @@ -115,8 +115,8 @@ function createShareOverlay(parentID, isGif, shareURL) { '
' + '
' + '
' + - '
' + - '' + + '
' + + '' + '
' + '' + '
' + @@ -152,13 +152,18 @@ function shareForUrl(selectedID) { data: paths[parseInt(selectedID.substring(1))] })); } -function shareWithEveryone(selectedID) { +function shareWithEveryone(selectedID, isGif) { selectedID = selectedID.id; // Why is this necessary? + document.getElementById(selectedID + "shareWithEveryoneButton").setAttribute("disabled", "disabled"); + document.getElementById(selectedID + "inviteConnectionsCheckbox").setAttribute("disabled", "disabled"); + EventBridge.emitWebEvent(JSON.stringify({ type: "snapshot", action: "shareSnapshotWithEveryone", - story_id: document.getElementById(selectedID).getAttribute("data-story-id") + story_id: document.getElementById(selectedID).getAttribute("data-story-id"), + isAnnouncement: document.getElementById(selectedID + "inviteConnectionsCheckbox").getAttribute("checked"), + isGif: isGif })); } function shareButtonClicked(selectedID) { @@ -233,7 +238,7 @@ window.onload = function () { var messageOptions = message.options; imageCount = message.image_data.length; message.image_data.forEach(function (element, idx, array) { - addImage(element, true, true, message.canShare); + addImage(element, true, true, message.canShare, message.image_data[idx].buttonDisabled); }); break; case 'addImages': diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 422c557391..95a76a9188 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -143,21 +143,38 @@ function onMessage(message) { case 'shareSnapshotWithEveryone': isLoggedIn = Account.isLoggedIn(); storyIDsToMaybeDelete.splice(storyIDsToMaybeDelete.indexOf(message.story_id), 1); + if (message.isGif) { + Settings.setValue("previousAnimatedSnapSharingDisabled", true); + } else { + Settings.setValue("previousStillSnapSharingDisabled", true); + } + + var requestBody = { + audience: "for_feed" + } + + if (message.isAnnouncement) { + requestBody.action = "announcement"; + } + if (isLoggedIn) { print('Modifying audience of story ID', message.story_id, "to 'for_feed'"); request({ uri: METAVERSE_BASE + '/api/v1/user_stories/' + message.story_id, method: 'PUT', json: true, - body: { - audience: "for_feed", - } + body: requestBody }, function (error, response) { if (error || (response.status !== 'success')) { print("ERROR changing audience: ", error || response.status); + if (message.isGif) { + Settings.setValue("previousAnimatedSnapSharingDisabled", false); + } else { + Settings.setValue("previousStillSnapSharingDisabled", false); + } return; } else { - print("SUCCESS changing audience!"); + print("SUCCESS changing audience" + (message.isAnnouncement ? " and posting announcement!" : "!")); } }); } else { @@ -195,8 +212,10 @@ var isInSnapshotReview = false; function openSnapApp() { var previousStillSnapPath = Settings.getValue("previousStillSnapPath"); var previousStillSnapStoryID = Settings.getValue("previousStillSnapStoryID"); + var previousStillSnapSharingDisabled = Settings.getValue("previousStillSnapSharingDisabled"); var previousAnimatedSnapPath = Settings.getValue("previousAnimatedSnapPath"); var previousAnimatedSnapStoryID = Settings.getValue("previousAnimatedSnapStoryID"); + var previousAnimatedSnapSharingDisabled = Settings.getValue("previousAnimatedSnapSharingDisabled"); snapshotOptions = { containsGif: previousAnimatedSnapPath !== "", processingGif: false, @@ -204,10 +223,10 @@ function openSnapApp() { } imageData = []; if (previousAnimatedSnapPath !== "") { - imageData.push({ localPath: previousAnimatedSnapPath, story_id: previousAnimatedSnapStoryID }); + imageData.push({ localPath: previousAnimatedSnapPath, story_id: previousAnimatedSnapStoryID, buttonDisabled: previousAnimatedSnapSharingDisabled }); } if (previousStillSnapPath !== "") { - imageData.push({ localPath: previousStillSnapPath, story_id: previousStillSnapStoryID }); + imageData.push({ localPath: previousStillSnapPath, story_id: previousStillSnapStoryID, buttonDisabled: previousStillSnapSharingDisabled }); } tablet.gotoWebScreen(SNAPSHOT_REVIEW_URL); tablet.webEventReceived.connect(onMessage); From ef2e5f78fff6c01a6badc1319729652acc7b6f3c Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Mon, 24 Apr 2017 17:41:37 -0700 Subject: [PATCH 57/76] Startings of needsLogin support --- scripts/system/snapshot.js | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 95a76a9188..0ac809f8a7 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -149,16 +149,16 @@ function onMessage(message) { Settings.setValue("previousStillSnapSharingDisabled", true); } - var requestBody = { - audience: "for_feed" - } - - if (message.isAnnouncement) { - requestBody.action = "announcement"; - } - if (isLoggedIn) { print('Modifying audience of story ID', message.story_id, "to 'for_feed'"); + var requestBody = { + audience: "for_feed" + } + + if (message.isAnnouncement) { + requestBody.action = "announcement"; + print('...Also announcing!'); + } request({ uri: METAVERSE_BASE + '/api/v1/user_stories/' + message.story_id, method: 'PUT', @@ -178,14 +178,10 @@ function onMessage(message) { } }); } else { - // TODO - /* needsLogin = true; shareAfterLogin = true; snapshotToShareAfterLogin = { path: message.data, href: message.href || href }; - */ } - /* if (needsLogin) { if ((HMD.active && Settings.getValue("hmdTabletBecomesToolbar")) || (!HMD.active && Settings.getValue("desktopTabletBecomesToolbar"))) { @@ -194,7 +190,7 @@ function onMessage(message) { tablet.loadQMLOnTop("../../dialogs/TabletLoginDialog.qml"); HMD.openTablet(); } - }*/ + } break; case 'shareButtonClicked': print('Twitter or FB "Share" button clicked! Removing ID', message.story_id, 'from storyIDsToMaybeDelete[].'); @@ -265,8 +261,10 @@ function takeSnapshot() { })); Settings.setValue("previousStillSnapPath", ""); Settings.setValue("previousStillSnapStoryID", ""); + Settings.setValue("previousStillSnapSharingDisabled", false); Settings.setValue("previousAnimatedSnapPath", ""); Settings.setValue("previousAnimatedSnapStoryID", ""); + Settings.setValue("previousAnimatedSnapSharingDisabled", false); // Raising the desktop for the share dialog at end will interact badly with clearOverlayWhenMoving. // Turn it off now, before we start futzing with things (and possibly moving). @@ -452,7 +450,7 @@ function onTabletScreenChanged(type, url) { isInSnapshotReview = false; } } -function onConnected() { +function onUsernameChanged() { if (shareAfterLogin && Account.isLoggedIn()) { print('Sharing snapshot after login:', snapshotToShareAfterLogin.path); Window.shareSnapshot(snapshotToShareAfterLogin.path, snapshotToShareAfterLogin.href); @@ -464,7 +462,7 @@ button.clicked.connect(openSnapApp); buttonConnected = true; Window.snapshotShared.connect(snapshotUploaded); tablet.screenChanged.connect(onTabletScreenChanged); -Account.usernameChanged.connect(onConnected); +Account.usernameChanged.connect(onUsernameChanged); Script.scriptEnding.connect(function () { if (buttonConnected) { button.clicked.disconnect(openSnapApp); From d7348aabc882e6284c9eeb77b86d00d2444165c7 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 25 Apr 2017 10:10:58 -0700 Subject: [PATCH 58/76] Quick comment updates --- scripts/system/html/js/SnapshotReview.js | 6 +++--- scripts/system/snapshot.js | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index 4c5419869d..c3fb20a2f9 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -131,7 +131,7 @@ function createShareOverlay(parentID, isGif, shareURL, hifiShareButtonsDisabled) return shareOverlayContainer; } function selectImageToShare(selectedID) { - selectedID = selectedID.id; // Why is this necessary? + selectedID = selectedID.id; // `selectedID` is passed as an HTML object to these functions; we just want the ID var shareOverlayContainer = document.getElementById(selectedID + "shareOverlayContainer"); var shareBar = document.getElementById(selectedID + "shareBar"); var shareOverlayBackground = document.getElementById(selectedID + "shareOverlayBackground"); @@ -153,7 +153,7 @@ function shareForUrl(selectedID) { })); } function shareWithEveryone(selectedID, isGif) { - selectedID = selectedID.id; // Why is this necessary? + selectedID = selectedID.id; // `selectedID` is passed as an HTML object to these functions; we just want the ID document.getElementById(selectedID + "shareWithEveryoneButton").setAttribute("disabled", "disabled"); document.getElementById(selectedID + "inviteConnectionsCheckbox").setAttribute("disabled", "disabled"); @@ -174,7 +174,7 @@ function shareButtonClicked(selectedID) { })); } function cancelSharing(selectedID) { - selectedID = selectedID.id; // Why is this necessary? + selectedID = selectedID.id; // `selectedID` is passed as an HTML object to these functions; we just want the ID var shareOverlayContainer = document.getElementById(selectedID + "shareOverlayContainer"); var shareBar = document.getElementById(selectedID + "shareBar"); var shareOverlayBackground = document.getElementById(selectedID + "shareOverlayBackground"); diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 0ac809f8a7..fe6ab3f28f 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -98,7 +98,7 @@ function onMessage(message) { var isLoggedIn; var needsLogin = false; switch (message.action) { - case 'ready': // Send it. + case 'ready': // DOM is ready and page has loaded tablet.emitScriptEvent(JSON.stringify({ type: "snapshot", action: "captureSettings", @@ -137,7 +137,7 @@ function onMessage(message) { print('Sharing snapshot with audience "for_url":', message.data); Window.shareSnapshot(message.data, message.href || href); } else { - // TODO? + // TODO } break; case 'shareSnapshotWithEveryone': From d461723012e180b1b84db48292a8951172ebda1c Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 25 Apr 2017 11:40:01 -0700 Subject: [PATCH 59/76] Thanks to Jess, slightly different Tweet language --- scripts/system/html/js/SnapshotReview.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index c3fb20a2f9..eec08a0e1e 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -122,7 +122,7 @@ function createShareOverlay(parentID, isGif, shareURL, hifiShareButtonsDisabled) '
' + '
' + '' + - '' + + '' + '
' + '
'; shareOverlayContainer.appendChild(shareOverlay); From 8c9cd0fad0c3b38193493c8a79b5927ae47fbdc6 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 25 Apr 2017 13:56:32 -0700 Subject: [PATCH 60/76] Custom social buttons; better testing functionality --- scripts/system/html/SnapshotReview.html | 18 ------------ scripts/system/html/css/SnapshotReview.css | 18 ++++++++++-- scripts/system/html/img/fb_logo.png | Bin 0 -> 1257 bytes scripts/system/html/img/twitter_logo.png | Bin 0 -> 552 bytes scripts/system/html/js/SnapshotReview.js | 32 +++++++++------------ 5 files changed, 29 insertions(+), 39 deletions(-) create mode 100644 scripts/system/html/img/fb_logo.png create mode 100644 scripts/system/html/img/twitter_logo.png diff --git a/scripts/system/html/SnapshotReview.html b/scripts/system/html/SnapshotReview.html index 400be4abdc..940adddd73 100644 --- a/scripts/system/html/SnapshotReview.html +++ b/scripts/system/html/SnapshotReview.html @@ -5,24 +5,6 @@ - diff --git a/scripts/system/html/css/SnapshotReview.css b/scripts/system/html/css/SnapshotReview.css index 859b4d8c90..0d1bd737e5 100644 --- a/scripts/system/html/css/SnapshotReview.css +++ b/scripts/system/html/css/SnapshotReview.css @@ -140,12 +140,26 @@ input[type=button].naked:active { width: 40%; margin-left: 10%; } -.externalShareControls { +.buttonShareControls { text-align: left; + height: 50px; + line-height: 50px; width: 40%; margin-right: 10%; } -.cancelShare { +.facebookButton { + background-image: url(../img/fb_logo.png); + width: 29px; + height: 29px; + display: inline-block; + margin-right: 8px; +} +.twitterButton { + background-image: url(../img/twitter_logo.png); + width: 29px; + height: 29px; + display: inline-block; + margin-right: 8px; } /* // END styling of share overlay diff --git a/scripts/system/html/img/fb_logo.png b/scripts/system/html/img/fb_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..1de20bacd8fb5719db3291365576d6368a5ff510 GIT binary patch literal 1257 zcmbVMTWl0n7(QC5q@|jWMiwtH8D1>y?#%4$&dv;bo1NJ$YuM6kx44mqGMzcw9oo4# zGj*qp1_ML_4>k(n5-tsiiSfl4LTZe`R*D1>$y#dyO^uc(e5f=9v2sbxlwI(l;lY!f z8-82B4;>;C8A*8a< zC=xd10-F_5m0YG=h@eH=HdqWRl}f%6>C?>~j9?hX)$sehrd;q^1DY*5Ud`H2RS=*h znTla6x`w!lVz=IJM^LnCK{Ya&$HJObX_Ta^m?Ih(;lnXib$u-vw(Kl?QpU1qD>q<3 zEDJ5Y-;`h$7B*DLj%qwTkqb038Fs6w^eCFxC+JeY3N#1l72don)SDk?NqHgO}Q8y62gF;@OxAIXydJhLV(a5-nH`|JzDKU^9cYk%%SL;T8keAC>Dovm2? z*1frSCL1C3k&nJYi8fw*!=Cj^TVTe!(4sS$f09vJMZ5g`mMPB z)0@99?y9}F{f&crZ8^C*JwLkmKr;4sU6}j(R_xwCcYeA(h_ W+`9J^=-T9dKIvqe@Ofg#?tcLbL$|sB literal 0 HcmV?d00001 diff --git a/scripts/system/html/img/twitter_logo.png b/scripts/system/html/img/twitter_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..59fd027c2a59cffb4c4f353dbc0ddce2cc3e92d4 GIT binary patch literal 552 zcmV+@0@wYCP)ca5h;T6e`N!ZW%_7Fh zh-L^Db6)RjfuD}F`pZz7)+*=?1MeO>1_Vr(aX5V=K z@A6xs&1GO<5aVQ2;b%fmI~ZW%B^;rP%iP^h{_K772fv|vpZwYO5T_dK=KlZBP{N0|RFI72{;g)@5^- zWs&5@629+${-1W`*WByB{$NjC2*8N7-ZT_pc9Uh%6JeI*Vq|7y`0(@p?T`PCJpa4v q@gE|SAa-*p2ScsC7&Vu+<^lkzl%{SoyW=zf0000' + '
' + @@ -118,15 +116,14 @@ function createShareOverlay(parentID, isGif, shareURL, hifiShareButtonsDisabled) '
' + '' + '
' + - '' + + '' + '
' + - '
' + - '' + - '' + + '
' + + '' + + '' + '
' + '
'; shareOverlayContainer.appendChild(shareOverlay); - twttr.widgets.load(shareOverlay); return shareOverlayContainer; } @@ -167,6 +164,7 @@ function shareWithEveryone(selectedID, isGif) { })); } function shareButtonClicked(selectedID) { + selectedID = selectedID.id; // `selectedID` is passed as an HTML object to these functions; we just want the ID EventBridge.emitWebEvent(JSON.stringify({ type: "snapshot", action: "shareButtonClicked", @@ -209,16 +207,6 @@ function handleCaptureSetting(setting) { } window.onload = function () { - // TESTING FUNCTIONS START - // Uncomment and modify the lines below to test SnapshotReview in a browser. - //imageCount = 1; - //addImage({ localPath: 'C:/Users/Zach Fox/Desktop/hifi-snap-by-zfox-on-2017-04-20_14-59-12.gif' }); - //addImage({ localPath: 'C:/Users/Zach Fox/Desktop/hifi-snap-by-zfox-on-2017-04-24_10-49-20.jpg' }); - //document.getElementById('p0').appendChild(createShareOverlay('p0', false, '')); - //addImage({ localPath: 'http://lorempixel.com/553/255' }); - //addImage({localPath: 'c:/Users/howar/OneDrive/Pictures/hifi-snap-by--on-2016-07-27_12-58-43.jpg'}); - // TESTING FUNCTIONS END - openEventBridge(function () { // Set up a handler for receiving the data, and tell the .js we are ready to receive it. EventBridge.scriptEventReceived.connect(function (message) { @@ -301,3 +289,9 @@ function takeSnapshot() { action: "takeSnapshot" })); } + +function testInBrowser() { + imageCount = 1; + //addImage({ localPath: 'http://lorempixel.com/553/255' }); + addImage({ localPath: 'C:/Users/Zach Fox/Desktop/hifi-snap-by-zfox-on-2017-04-25_11-35-13.jpg' }, false, true, true, false); +} From 92187e2424b952952f018fa27576e29fa5bee40e Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 26 Apr 2017 12:08:22 -0700 Subject: [PATCH 61/76] Making progress towards file chooser dialog --- scripts/system/html/SnapshotReview.html | 4 +- scripts/system/html/css/SnapshotReview.css | 60 ++++++++++++++++----- scripts/system/html/img/snapshotIcon.png | Bin 0 -> 16358 bytes scripts/system/html/js/SnapshotReview.js | 44 ++++++++++++--- scripts/system/snapshot.js | 32 ++++++++--- 5 files changed, 110 insertions(+), 30 deletions(-) create mode 100644 scripts/system/html/img/snapshotIcon.png diff --git a/scripts/system/html/SnapshotReview.html b/scripts/system/html/SnapshotReview.html index 940adddd73..9469a9d313 100644 --- a/scripts/system/html/SnapshotReview.html +++ b/scripts/system/html/SnapshotReview.html @@ -27,8 +27,8 @@
-
-
+ +
diff --git a/scripts/system/html/css/SnapshotReview.css b/scripts/system/html/css/SnapshotReview.css index 0d1bd737e5..9db7e35b9f 100644 --- a/scripts/system/html/css/SnapshotReview.css +++ b/scripts/system/html/css/SnapshotReview.css @@ -8,12 +8,6 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html */ -body { - padding: 0; - margin: 0; - overflow: hidden; -} - /* // START styling of top bar and its contents */ @@ -60,6 +54,17 @@ input[type=button].naked:active { // END styling of top bar and its contents */ +/* +// START styling of snapshot instructions panel +*/ +.snapshotInstructions { + font-family: Raleway-Regular; + margin: 0 20px; +} +/* +// END styling of snapshot instructions panel +*/ + /* // START styling of snapshot pane and its contents */ @@ -172,17 +177,18 @@ input[type=button].naked:active { // START styling of snapshot controls (bottom panel) and its contents */ #snapshot-controls { - padding-left: 8px; - padding-right: 8px; width: 100%; position: absolute; left: 0; margin-top: 8px; overflow: hidden; + display: flex; + justify-content: center; } #snap-settings { - float: left; - margin-left: 10px; + display: inline; + width: 150px; + margin: auto; } #snap-settings form input { margin-bottom: 5px; @@ -191,18 +197,28 @@ input[type=button].naked:active { #snap-button { width: 65px; height: 65px; + padding: 0; border-radius: 50%; background: #EA4C5F; border: 3px solid white; - margin-left: auto; - margin-right: auto; + margin: auto; + box-sizing: content-box; + display: inline; } -#snap-button:hover { +#snap-button:disabled { + background: gray; +} +#snap-button:hover:enabled { background: #C62147; } -#snap-button:active { +#snap-button:active:enabled { background: #EA4C5F; } +#snap-settings-right { + display: inline; + width: 150px; + margin: auto; +} /* // END styling of snapshot controls (bottom panel) and its contents */ @@ -210,6 +226,22 @@ input[type=button].naked:active { /* // START misc styling */ +body { + padding: 0; + margin: 0; + overflow: hidden; +} +p { + margin: 2px 0; +} +h4 { + margin: 14px 0 0 0; +} +.centeredImage { + margin: 0 auto; + display: block; +} + .prompt { font-family: Raleway-SemiBold; font-size: 14px; diff --git a/scripts/system/html/img/snapshotIcon.png b/scripts/system/html/img/snapshotIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..5cb2742a32bf362a6cf1e230e9ebad1ac8feffba GIT binary patch literal 16358 zcmeI3X;>529>yn76mUTV1Qj&~H^h)hAcTxz51SIi2m&I=FqwoAk_pKGVTbCiQmED{ z6_p~bC>2-S6_;ogyHHWE(t2G`^ja-eDpstw$bG^Z5WLQZew+7sk`L#9-uImIn{(#m zOY%d4{l}P2G6eu&OkjXe3;=+q?qfUx0Dwv?=?(w@CdzrMF`iYQW6&{)8zY=60!2z6G#(2` zSD{PNgF}(@1cWbPc(|JiG>}HFl%oVp*T@qUYDnY8=*oo@YDm}2V$i#~5D9J!FWrFj z2vG>#2UnqVuA>VRVLQ3fd3;AUm+#7Po=4|^Y!?>DXR)1`AR7Yt5Xh$Yh~aKZr_%*0 zi4+nG{d$H|Nu+L!c!E$uELLi2s$;5?Bd(IM*nB>p1#(y%4pSwOGSz7c0@g4UYP;@` z-W(ySMpT%Rz;Fd!mkYH>3^aj>&17i64H=k4nw^yT!%>RI28 zMO9b|D)Cd{@~$(kgjH%(cXZqsy=O;%7wBFMU55|C-d<|Ye7uLB&s*01`Sd4ChFz)8 z|4Y`h_9ypY=wGUSRU_A-U!=lhm;z4JW2Jk7WH1jAp*M-{VFEBUfveK=3cWAeC1}#X z*4{!Q_MQ)>TY|1v8U!P{*S5PFPC+FC)_}JDN%g76G{iG~zwO(duV){AjMR8kG5EOa zAJy)tu3p23sBtNg3ad~r8LA-O=S|d|I?y(JJQ5EpWT?cQ)l06oVX#HO(%p-R*uX+} z>%a%vy9wXk00IdDNpY1NCfqSOEJImJg-pQeZ|oma(03O=K6oOo65$fm-AT}YMd&r_ zxpcRHZgZjv0`A%rY(dw}FraO4{|MdbPfYIW&*Oj~2(s9^?;fjud9XF|-B$hbU~9iK zOo0*ZoSxAJHS4)_S5^0_mndNbb$8)%ow$Oo8%)1>upfa!krIWAlqg)JMBySO3KuC+xJZe@MM@MdQlfB? z5`~MDC|smO;UXmp7b#J=NQuHlN)#?qqHvKCg^QFZT%<(dA|(nJDN(pciNZxn6fRPt zaFG&)ih|C^0`t zjNf`>S$=+b{#T2e3f6k2Z}f)oz2Ja)&vO$VOPX=weXJ@uJed&ZT{H@~}?wKw_;fBi9ZSy@_*!EuA*zdozd zf=!d;=Zs5e**EHqGb_$z>@|=Yw&hjl+I!wz-8?@R&#TLmYiEZWN~YBv(0-ytv_4u< zY1JFx^q9mE*)M9sgqfQbnE%4~*A6BmleR>LYk?-Crrm3^{d2RlUS%fnM!|+T*nd4^ zKL{BcG(urmnYYdE)eYL7yte2ngQ?>-G*9oW&o)o<6IOC+7w;6jTvMxFURA!;j(PCU z>A80-HY{6oVCli@bDw2~h8r8bu-@=R8qhLs!^CheWt8;S<&HOZ+Z_1V!0+RLjYz$V zdE`>yygYK3I%=hOZ;Tb_Eev*@l`w9@wPAnnJ{c8FKNX%$2VDc08W1*%(&~7fnAOL%lqilq@T3BymSZf z`=cf|ig(uAAo=@5Rhy!GAB352nayYxO4hF3l>aSZ9_h%a7Pl_9%sYJT#et-!w8^51 zF!P|<<-n<6qjlnz4#x8=gR}m2ul8{|c&99t;JyFwSYNibStw$6GM*;|-Fd{g52QE= z;;ljykZl1?nPPD7(MPQlXEMI=zS(}cDQAIjgkO`Jv}V)DMR}OC`PGGH#}y-6z&ZsG zW0-9a9FGB?Lp61a_kgAgEvGs(94Yy6y4y`nv8~Zi#Sh{m8;``EYY7m%Rwfk9HD>MR&522+5mbRDW-pzA4 z{Pz#z3y+_?)G+anl2pmj{jEls;+&4gRkQH%on^Pr*Uc|ljm@?jQ=b9wp&cjBpSa%i ztw8o85>v%cnZ`SPf|gne8m>3i*fttxN=tu|T#Q3kj+)z4-hAS@*ZOU*=WCGQPwI;vR5*&5n8$pJR#=b7Q)k<02TpRO<_JeRuM#o*NEwdUT9wu#1 zv$m0y?h`N3tUDHcbq`o=+Z>Z4hz!11-Z9?s{G_9!w;Rn`{UQp; ztUQ^2;=0M-Q|ixxcg-gkn%pSfQIVhX?9Ycg>?fY7IkVFG?@HN=8Oe(;?|i^@xF0bl z{dB5fb#(rQ32mjJ{-?wJ%QHeE?(e$!LT)mu;2iuUf#H1a$m9n!kEGD zvUP3d9L7^d{dC4=(-4Swlru9S?qZSc?aG=3x7r>2zu_Kgh|JA7W7aykRVF;=w!HLO zwuNR!qS341bt!1z7q-4am*PC@xSF==g%iIjUUlS4jn{>HIh$77OenD3FV8X?clOS` zk67PNF0_yNY;Md(hnP+CVv3kCpF75EUbOs4`fnpGI;$5+Kb|JC^4Rv' + + '
' + + '

This app lets you take and share snaps and GIFs with your connections in High Fidelity.

' + + "

Setup Instructions

" + + "

Before you can begin taking snaps, please choose where you'd like to save snaps on your computer:

" + + '
' + + ''; + document.getElementById("snap-button").disabled = true; +} +function chooseSnapshotLocation() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "chooseSnapshotLocation" + })); +} +function clearImages() { + document.getElementById("snap-button").disabled = false; + var snapshotImagesDiv = document.getElementById("snapshot-images"); + snapshotImagesDiv.classList.remove("snapshotInstructions"); while (snapshotImagesDiv.hasChildNodes()) { snapshotImagesDiv.removeChild(snapshotImagesDiv.lastChild); } @@ -207,6 +227,7 @@ function handleCaptureSetting(setting) { } window.onload = function () { + testInBrowser(false); openEventBridge(function () { // Set up a handler for receiving the data, and tell the .js we are ready to receive it. EventBridge.scriptEventReceived.connect(function (message) { @@ -218,6 +239,12 @@ window.onload = function () { } switch (message.action) { + case 'showSetupInstructions': + showSetupInstructions(); + break; + case 'snapshotLocationChosen': + clearImages(); + break; case 'clearPreviousImages': clearImages(); break; @@ -274,8 +301,7 @@ window.onload = function () { type: "snapshot", action: "ready" })); - }); - + });; }; function snapshotSettings() { EventBridge.emitWebEvent(JSON.stringify({ @@ -290,8 +316,12 @@ function takeSnapshot() { })); } -function testInBrowser() { - imageCount = 1; - //addImage({ localPath: 'http://lorempixel.com/553/255' }); - addImage({ localPath: 'C:/Users/Zach Fox/Desktop/hifi-snap-by-zfox-on-2017-04-25_11-35-13.jpg' }, false, true, true, false); +function testInBrowser(isTestingSetupInstructions) { + if (isTestingSetupInstructions) { + showSetupInstructions(); + } else { + imageCount = 1; + //addImage({ localPath: 'http://lorempixel.com/553/255' }); + addImage({ localPath: 'C:/Users/valef/Desktop/hifi-snap-by-zfox-on-2017-04-26_10-26-53.jpg' }, false, true, true, false); + } } diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index fe6ab3f28f..1f685924f6 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -104,13 +104,31 @@ function onMessage(message) { action: "captureSettings", setting: Settings.getValue("alsoTakeAnimatedSnapshot", true) })); - tablet.emitScriptEvent(JSON.stringify({ - type: "snapshot", - action: "showPreviousImages", - options: snapshotOptions, - image_data: imageData, - canShare: !!isDomainOpen(Settings.getValue("previousSnapshotDomainID")) - })); + if (Settings.getValue("snapshotsLocation", "") !== "") { + tablet.emitScriptEvent(JSON.stringify({ + type: "snapshot", + action: "showPreviousImages", + options: snapshotOptions, + image_data: imageData, + canShare: !!isDomainOpen(Settings.getValue("previousSnapshotDomainID")) + })); + } else { + tablet.emitScriptEvent(JSON.stringify({ + type: "snapshot", + action: "showSetupInstructions" + })); + } + break; + case 'chooseSnapshotLocation': + var snapshotPath = Window.browse("Choose Snapshots Directory","",""); + + if (!snapshotPath.isEmpty()) { // not cancelled + Settings.setValue("snapshotsLocation", snapshotPath); + tablet.emitScriptEvent(JSON.stringify({ + type: "snapshot", + action: "snapshotLocationChosen" + })); + } break; case 'openSettings': if ((HMD.active && Settings.getValue("hmdTabletBecomesToolbar")) From 322121428398fb926658c73ba2c1571471a11699 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 26 Apr 2017 12:38:38 -0700 Subject: [PATCH 62/76] New Snap FTUE flow --- .../scripting/WindowScriptingInterface.cpp | 22 +++++++++++++++++++ .../src/scripting/WindowScriptingInterface.h | 1 + scripts/system/html/css/SnapshotReview.css | 1 + scripts/system/html/js/SnapshotReview.js | 9 ++++++++ scripts/system/snapshot.js | 10 +++++++-- 5 files changed, 41 insertions(+), 2 deletions(-) diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index 2df825d643..1e14c24da3 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -168,6 +168,28 @@ void WindowScriptingInterface::ensureReticleVisible() const { } } +/// Display a "browse to directory" dialog. If `directory` is an invalid file or directory the browser will start at the current +/// working directory. +/// \param const QString& title title of the window +/// \param const QString& directory directory to start the file browser at +/// \param const QString& nameFilter filter to filter filenames by - see `QFileDialog` +/// \return QScriptValue file path as a string if one was selected, otherwise `QScriptValue::NullValue` +QScriptValue WindowScriptingInterface::browseDir(const QString& title, const QString& directory) { + ensureReticleVisible(); + QString path = directory; + if (path.isEmpty()) { + path = getPreviousBrowseLocation(); + } +#ifndef Q_OS_WIN + path = fixupPathForMac(directory); +#endif + QString result = OffscreenUi::getExistingDirectory(nullptr, title, path); + if (!result.isEmpty()) { + setPreviousBrowseLocation(QFileInfo(result).absolutePath()); + } + return result.isEmpty() ? QScriptValue::NullValue : QScriptValue(result); +} + /// Display an open file dialog. If `directory` is an invalid file or directory the browser will start at the current /// working directory. /// \param const QString& title title of the window diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index 7641110972..2b1e48d918 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -51,6 +51,7 @@ public slots: QScriptValue confirm(const QString& message = ""); QScriptValue prompt(const QString& message = "", const QString& defaultText = ""); CustomPromptResult customPrompt(const QVariant& config); + QScriptValue browseDir(const QString& title = "", const QString& directory = ""); QScriptValue browse(const QString& title = "", const QString& directory = "", const QString& nameFilter = ""); QScriptValue save(const QString& title = "", const QString& directory = "", const QString& nameFilter = ""); QScriptValue browseAssets(const QString& title = "", const QString& directory = "", const QString& nameFilter = ""); diff --git a/scripts/system/html/css/SnapshotReview.css b/scripts/system/html/css/SnapshotReview.css index 9db7e35b9f..ef737870e0 100644 --- a/scripts/system/html/css/SnapshotReview.css +++ b/scripts/system/html/css/SnapshotReview.css @@ -204,6 +204,7 @@ input[type=button].naked:active { margin: auto; box-sizing: content-box; display: inline; + outline:none; } #snap-button:disabled { background: gray; diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index 9ee45281ac..418e623aa8 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -25,6 +25,14 @@ function showSetupInstructions() { ''; document.getElementById("snap-button").disabled = true; } +function showSetupComplete() { + var snapshotImagesDiv = document.getElementById("snapshot-images"); + snapshotImagesDiv.className = "snapshotInstructions"; + snapshotImagesDiv.innerHTML = 'Snapshot Instructions' + + '
' + + "

You're all set!

" + + '

Try taking a snapshot by pressing the button below.

'; +} function chooseSnapshotLocation() { EventBridge.emitWebEvent(JSON.stringify({ type: "snapshot", @@ -244,6 +252,7 @@ window.onload = function () { break; case 'snapshotLocationChosen': clearImages(); + showSetupComplete(); break; case 'clearPreviousImages': clearImages(); diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 1f685924f6..88014d5c50 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -117,12 +117,18 @@ function onMessage(message) { type: "snapshot", action: "showSetupInstructions" })); + Settings.setValue("previousStillSnapPath", ""); + Settings.setValue("previousStillSnapStoryID", ""); + Settings.setValue("previousStillSnapSharingDisabled", false); + Settings.setValue("previousAnimatedSnapPath", ""); + Settings.setValue("previousAnimatedSnapStoryID", ""); + Settings.setValue("previousAnimatedSnapSharingDisabled", false); } break; case 'chooseSnapshotLocation': - var snapshotPath = Window.browse("Choose Snapshots Directory","",""); + var snapshotPath = Window.browseDir("Choose Snapshots Directory", "", ""); - if (!snapshotPath.isEmpty()) { // not cancelled + if (snapshotPath) { // not cancelled Settings.setValue("snapshotsLocation", snapshotPath); tablet.emitScriptEvent(JSON.stringify({ type: "snapshot", From 6711e8cbc2cdf3217ef3ae4d0250daa3eabc9be8 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 26 Apr 2017 13:01:02 -0700 Subject: [PATCH 63/76] Remove duplicate setting; Make settings page open; button state --- interface/src/ui/PreferencesDialog.cpp | 5 -- scripts/system/snapshot.js | 64 ++++++++++++++------------ 2 files changed, 35 insertions(+), 34 deletions(-) diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index a12d9020ae..617ac1ed1c 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -116,11 +116,6 @@ void setupPreferences() { auto preference = new BrowsePreference(SNAPSHOTS, "Put my snapshots here", getter, setter); preferences->addPreference(preference); } - { - auto getter = []()->bool { return SnapshotAnimated::alsoTakeAnimatedSnapshot.get(); }; - auto setter = [](bool value) { SnapshotAnimated::alsoTakeAnimatedSnapshot.set(value); }; - preferences->addPreference(new CheckPreference(SNAPSHOTS, "Take Animated GIF Snapshot", getter, setter)); - } { auto getter = []()->float { return SnapshotAnimated::snapshotAnimatedDuration.get(); }; auto setter = [](float value) { SnapshotAnimated::snapshotAnimatedDuration.set(value); }; diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 88014d5c50..4cb1232a58 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -24,6 +24,7 @@ var buttonConnected = false; var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); var button = tablet.addButton({ icon: "icons/tablet-icons/snap-i.svg", + activeIcon: "icons/tablet-icons/snap-a.svg", text: buttonName, sortOrder: 5 }); @@ -137,8 +138,7 @@ function onMessage(message) { } break; case 'openSettings': - if ((HMD.active && Settings.getValue("hmdTabletBecomesToolbar")) - || (!HMD.active && Settings.getValue("desktopTabletBecomesToolbar"))) { + if ((HMD.active && Settings.getValue("hmdTabletBecomesToolbar", true)) || (!HMD.active && Settings.getValue("desktopTabletBecomesToolbar", false))) { Desktop.show("hifi/dialogs/GeneralPreferencesDialog.qml", "General Preferences"); } else { tablet.loadQMLOnTop("TabletGeneralPreferences.qml"); @@ -229,29 +229,34 @@ function onMessage(message) { var SNAPSHOT_REVIEW_URL = Script.resolvePath("html/SnapshotReview.html"); var isInSnapshotReview = false; -function openSnapApp() { - var previousStillSnapPath = Settings.getValue("previousStillSnapPath"); - var previousStillSnapStoryID = Settings.getValue("previousStillSnapStoryID"); - var previousStillSnapSharingDisabled = Settings.getValue("previousStillSnapSharingDisabled"); - var previousAnimatedSnapPath = Settings.getValue("previousAnimatedSnapPath"); - var previousAnimatedSnapStoryID = Settings.getValue("previousAnimatedSnapStoryID"); - var previousAnimatedSnapSharingDisabled = Settings.getValue("previousAnimatedSnapSharingDisabled"); - snapshotOptions = { - containsGif: previousAnimatedSnapPath !== "", - processingGif: false, - shouldUpload: false +function onButtonClicked() { + if (isInSnapshotReview){ + // for toolbar-mode: go back to home screen, this will close the window. + tablet.gotoHomeScreen(); + } else { + var previousStillSnapPath = Settings.getValue("previousStillSnapPath"); + var previousStillSnapStoryID = Settings.getValue("previousStillSnapStoryID"); + var previousStillSnapSharingDisabled = Settings.getValue("previousStillSnapSharingDisabled"); + var previousAnimatedSnapPath = Settings.getValue("previousAnimatedSnapPath"); + var previousAnimatedSnapStoryID = Settings.getValue("previousAnimatedSnapStoryID"); + var previousAnimatedSnapSharingDisabled = Settings.getValue("previousAnimatedSnapSharingDisabled"); + snapshotOptions = { + containsGif: previousAnimatedSnapPath !== "", + processingGif: false, + shouldUpload: false + } + imageData = []; + if (previousAnimatedSnapPath !== "") { + imageData.push({ localPath: previousAnimatedSnapPath, story_id: previousAnimatedSnapStoryID, buttonDisabled: previousAnimatedSnapSharingDisabled }); + } + if (previousStillSnapPath !== "") { + imageData.push({ localPath: previousStillSnapPath, story_id: previousStillSnapStoryID, buttonDisabled: previousStillSnapSharingDisabled }); + } + tablet.gotoWebScreen(SNAPSHOT_REVIEW_URL); + tablet.webEventReceived.connect(onMessage); + HMD.openTablet(); + isInSnapshotReview = true; } - imageData = []; - if (previousAnimatedSnapPath !== "") { - imageData.push({ localPath: previousAnimatedSnapPath, story_id: previousAnimatedSnapStoryID, buttonDisabled: previousAnimatedSnapSharingDisabled }); - } - if (previousStillSnapPath !== "") { - imageData.push({ localPath: previousStillSnapPath, story_id: previousStillSnapStoryID, buttonDisabled: previousStillSnapSharingDisabled }); - } - tablet.gotoWebScreen(SNAPSHOT_REVIEW_URL); - tablet.webEventReceived.connect(onMessage); - HMD.openTablet(); - isInSnapshotReview = true; } function snapshotUploaded(isError, reply) { @@ -315,7 +320,7 @@ function takeSnapshot() { Window.stillSnapshotTaken.connect(stillSnapshotTaken); } if (buttonConnected) { - button.clicked.disconnect(openSnapApp); + button.clicked.disconnect(onButtonClicked); buttonConnected = false; } @@ -366,7 +371,7 @@ function stillSnapshotTaken(pathStillSnapshot, notify) { } Window.stillSnapshotTaken.disconnect(stillSnapshotTaken); if (!buttonConnected) { - button.clicked.connect(openSnapApp); + button.clicked.connect(onButtonClicked); buttonConnected = true; } @@ -430,7 +435,7 @@ function processingGifStarted(pathStillSnapshot) { function processingGifCompleted(pathAnimatedSnapshot) { Window.processingGifCompleted.disconnect(processingGifCompleted); if (!buttonConnected) { - button.clicked.connect(openSnapApp); + button.clicked.connect(onButtonClicked); buttonConnected = true; } @@ -469,6 +474,7 @@ function maybeDeleteSnapshotStories() { } } function onTabletScreenChanged(type, url) { + button.editProperties({ isActive: !isInSnapshotReview }); if (isInSnapshotReview) { tablet.webEventReceived.disconnect(onMessage); isInSnapshotReview = false; @@ -482,14 +488,14 @@ function onUsernameChanged() { } } -button.clicked.connect(openSnapApp); +button.clicked.connect(onButtonClicked); buttonConnected = true; Window.snapshotShared.connect(snapshotUploaded); tablet.screenChanged.connect(onTabletScreenChanged); Account.usernameChanged.connect(onUsernameChanged); Script.scriptEnding.connect(function () { if (buttonConnected) { - button.clicked.disconnect(openSnapApp); + button.clicked.disconnect(onButtonClicked); buttonConnected = false; } if (tablet) { From b26f31704b7591e735e6b8fd393407aafa543758 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 26 Apr 2017 13:17:00 -0700 Subject: [PATCH 64/76] Fix button state; use request() --- scripts/system/snapshot.js | 68 ++++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 32 deletions(-) diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 4cb1232a58..06d519969e 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -111,7 +111,7 @@ function onMessage(message) { action: "showPreviousImages", options: snapshotOptions, image_data: imageData, - canShare: !!isDomainOpen(Settings.getValue("previousSnapshotDomainID")) + canShare: !isDomainOpen(Settings.getValue("previousSnapshotDomainID")) })); } else { tablet.emitScriptEvent(JSON.stringify({ @@ -229,11 +229,13 @@ function onMessage(message) { var SNAPSHOT_REVIEW_URL = Script.resolvePath("html/SnapshotReview.html"); var isInSnapshotReview = false; +var shouldActivateButton = false; function onButtonClicked() { if (isInSnapshotReview){ // for toolbar-mode: go back to home screen, this will close the window. tablet.gotoHomeScreen(); } else { + shouldActivateButton = true; var previousStillSnapPath = Settings.getValue("previousStillSnapPath"); var previousStillSnapStoryID = Settings.getValue("previousStillSnapStoryID"); var previousStillSnapSharingDisabled = Settings.getValue("previousStillSnapSharingDisabled"); @@ -343,7 +345,7 @@ function isDomainOpen(id) { if (!id) { return false; } - var request = new XMLHttpRequest(); + var options = [ 'now=' + new Date().toISOString(), 'include_actions=concurrency', @@ -351,15 +353,19 @@ function isDomainOpen(id) { 'restriction=open,hifi' // If we're sharing, we're logged in // If we're here, protocol matches, and it is online ]; - var url = location.metaverseServerUrl + "/api/v1/user_stories?" + options.join('&'); - request.open("GET", url, false); - request.send(); - if (request.status !== 200) { - return false; - } - var response = JSON.parse(request.response); // Not parsed for us. - return (response.status === 'success') && - response.total_entries; + var url = METAVERSE_BASE + "/api/v1/user_stories?" + options.join('&'); + + return request({ + uri: url, + method: 'GET' + }, function (error, response) { + if (error || (response.status !== 'success')) { + print("ERROR getting open status of domain: ", error || response.status); + return false; + } else { + return response.total_entries; + } + }); } function stillSnapshotTaken(pathStillSnapshot, notify) { @@ -382,7 +388,7 @@ function stillSnapshotTaken(pathStillSnapshot, notify) { snapshotOptions = { containsGif: false, processingGif: false, - canShare: !!isDomainOpen(domainId) + canShare: !isDomainOpen(domainId) }; imageData = [{ localPath: pathStillSnapshot, href: href }]; Settings.setValue("previousStillSnapPath", pathStillSnapshot); @@ -414,7 +420,7 @@ function processingGifStarted(pathStillSnapshot) { containsGif: true, processingGif: true, loadingGifPath: Script.resolvePath(Script.resourcesPath() + 'icons/loadingDark.gif'), - canShare: !!isDomainOpen(domainId) + canShare: !isDomainOpen(domainId) }; imageData = [{ localPath: pathStillSnapshot, href: href }]; Settings.setValue("previousStillSnapPath", pathStillSnapshot); @@ -442,7 +448,7 @@ function processingGifCompleted(pathAnimatedSnapshot) { snapshotOptions = { containsGif: true, processingGif: false, - canShare: !!isDomainOpen(domainId) + canShare: !isDomainOpen(domainId) } imageData = [{ localPath: pathAnimatedSnapshot, href: href }]; Settings.setValue("previousAnimatedSnapPath", pathAnimatedSnapshot); @@ -455,26 +461,24 @@ function processingGifCompleted(pathAnimatedSnapshot) { })); } function maybeDeleteSnapshotStories() { - if (storyIDsToMaybeDelete.length > 0) { - print("User took new snapshot & didn't share old one(s); deleting old snapshot stories"); - storyIDsToMaybeDelete.forEach(function (element, idx, array) { - request({ - uri: METAVERSE_BASE + '/api/v1/user_stories/' + element, - method: 'DELETE' - }, function (error, response) { - if (error || (response.status !== 'success')) { - print("ERROR deleting snapshot story: ", error || response.status); - return; - } else { - print("SUCCESS deleting snapshot story with ID", element); - } - }) - }); - storyIDsToMaybeDelete = []; - } + storyIDsToMaybeDelete.forEach(function (element, idx, array) { + request({ + uri: METAVERSE_BASE + '/api/v1/user_stories/' + element, + method: 'DELETE' + }, function (error, response) { + if (error || (response.status !== 'success')) { + print("ERROR deleting snapshot story: ", error || response.status); + return; + } else { + print("SUCCESS deleting snapshot story with ID", element); + } + }) + }); + storyIDsToMaybeDelete = []; } function onTabletScreenChanged(type, url) { - button.editProperties({ isActive: !isInSnapshotReview }); + button.editProperties({ isActive: shouldActivateButton }); + shouldActivateButton = false; if (isInSnapshotReview) { tablet.webEventReceived.disconnect(onMessage); isInSnapshotReview = false; From a24a48843fdb6c692d098bdb7ffcbfc6b031bee1 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 26 Apr 2017 16:22:01 -0700 Subject: [PATCH 65/76] Checkpoint --- scripts/system/html/css/SnapshotReview.css | 133 ++++++++++------ scripts/system/html/img/shareIcon.png | Bin 0 -> 15201 bytes scripts/system/html/js/SnapshotReview.js | 177 ++++++++++----------- scripts/system/snapshot.js | 83 ++++++++-- 4 files changed, 234 insertions(+), 159 deletions(-) create mode 100644 scripts/system/html/img/shareIcon.png diff --git a/scripts/system/html/css/SnapshotReview.css b/scripts/system/html/css/SnapshotReview.css index ef737870e0..fdfc3bfca9 100644 --- a/scripts/system/html/css/SnapshotReview.css +++ b/scripts/system/html/css/SnapshotReview.css @@ -86,71 +86,49 @@ input[type=button].naked:active { } .gifLabel { + position:absolute; + left: 15px; + top: 10px; font-family: Raleway-SemiBold; font-size: 18px; color: white; - float: left; text-shadow: 2px 2px 3px #000000; - margin-left: 20px; -} -.shareButtonDiv { - display: flex; - align-items: center; - font-family: Raleway-SemiBold; - font-size: 14px; - color: white; - float: right; - text-shadow: 2px 2px 3px #000000; - width: 100px; - height: 100%; - margin-right: 10px; -} -.shareButtonLabel { - vertical-align: middle; -} -.shareButton { - background-color: white; - width: 40px; - height: 40px; - border-radius: 50%; - border-width: 0; - margin-left: 5px; -} -.shareButton:hover { - background-color: #afafaf; -} -.shareButton:active { - background-color: white; } +/* +// END styling of snapshot pane and its contents +*/ /* -// START styling of share overlay +// START styling of share bar */ -.shareOverlayDiv { - text-align: center; -} .shareControls { - text-align: left; display: flex; - justify-content: center; + justify-content: space-between; flex-direction: row; - align-items: flex-start; + align-items: center; height: 50px; + line-height: 60px; + width: calc(100% - 8px); + position: absolute; + bottom: 4px; + left: 4px; + right: 4px; } -.shareOverlayLabel { - line-height: 75px; +.shareButtons { + display: flex; + align-items: center; + margin-left: 40px; + height: 100%; } -.hifiShareControls { +.blastToConnections { text-align: left; - width: 40%; - margin-left: 10%; + margin-right: 25px; + height: 29px; } -.buttonShareControls { +.shareWithEveryone { text-align: left; - height: 50px; - line-height: 50px; - width: 40%; - margin-right: 10%; + margin-right: 8px; + height: 29px; } .facebookButton { background-image: url(../img/fb_logo.png); @@ -166,12 +144,65 @@ input[type=button].naked:active { display: inline-block; margin-right: 8px; } +.showShareButtonsButtonDiv { + display: inline-flex; + align-items: center; + font-family: Raleway-SemiBold; + font-size: 14px; + color: white; + text-shadow: 2px 2px 3px #000000; + height: 100%; + margin-right: 10px; +} +.showShareButton { + width: 40px; + height: 40px; + border-radius: 50%; + border-width: 0; + margin-left: 5px; + outline: none; +} +.showShareButton.active { + border-color: #00b4ef; + border-width: 3px; + background-color: white; +} +.showShareButton.active:hover { + background-color: #afafaf; +} +.showShareButton.active:active { + background-color: white; +} +.showShareButton.inactive { + border-width: 0; + background-color: white; +} +.showShareButton.inactive:hover { + background-color: #afafaf; +} +.showShareButton.inactive:active { + background-color: white; +} +.showShareButtonDots { + display: flex; + width: 32px; + height: 40px; + position: absolute; + right: 14px; + pointer-events: none; +} +.showShareButtonDots > span { + width: 10px; + height: 10px; + margin: auto; + background-color: #0093C5; + border-radius: 50%; + border-width: 0; + display: inline; +} /* // END styling of share overlay */ -/* -// END styling of snapshot pane and its contents -*/ /* // START styling of snapshot controls (bottom panel) and its contents diff --git a/scripts/system/html/img/shareIcon.png b/scripts/system/html/img/shareIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..0486ac920222964fc18287bd03131bc70ed942bd GIT binary patch literal 15201 zcmeI3TZ|J`7{|{F5-IM!pkbp3!>mRyPUq6=jMFYlyU<3uuFINTObkqCPP-G@nPFzS z?d}u0(FBA@l&l7fl1PX?NRS6_5Wx_m5i}A*P*CK-#E1%-5LbvsGrhOF-RueB#hhQ# zK78N#f8U({xlCWWy}xf=yXzho0H8e{iw*z)I-SoQ3jhF@@}a)~01J)SMhk#Ni=9s! z*!yZH0Q|4&;IKWMSTD$W)-5S|61odn12~A?1w)dzK$}Ryw5o;3t4EKMgsOzdAzy+? z7!f$4#>P#!VZ3ip9^WGK3fUcYi3OpMHL}o_h(b1_SwbO17V8R{B{*T4B#J}qEg`bU zX-Etw`iY2cLc-_vQ!?ub5&_=L`uHH{eVE`F)=x7$&3Y+@6&PM%SfUIWb`b<2no3F- zh^{TSGnG_`9I>ll3fJkPMo2Gjt14A~Cxxo1IZ<*}bFo>`H5J z%z*R&wDg=Q!`@M-*`q?#MxB=V&#yk zDSF;oCDP53+IrWj!Kh@zu%o0HFU9!>S)btJ1h0=`0s_O-6)BBus*!-Uq)4_jmnsco zo9pO`ni`u^mD z&&HEgxe%tGL{X82lx}7vJFI4r~Fm z$+;z$%@4nRzs|L{IGM{Gr8jU0LL&4L$l6zSz4cNmTsI~TAyx~)|5|ZsvYLa z-I~I6D(4Man)L=ll402I^Lcz?@x@vnZdUZG1(+F*s#&O6&V9vs%bMoIjKYnrgmsCs zu(TEGewGyzrRYF(ZP+E!)gPP{&8?+2qgx1Wf&{h;32YY<*e)cnT}WWNkid2!f$c&9 z+l2(S3khr&64)*zuw6)CyO6+kA%X2e0^5ZIwhIYt7ZTVmB(PmbV7rjOb|Hc7LIT@` z1hxwaY!?#PE+nvBNMO5=z;+>l?Lq?Eg#@+>32YY<*e)cnT}WWNkid2!F{@p!+Fv-K zri92m*&Y7qaNr;SfRJMY2>`|)0ASk$0N4KoVB60C@K3jnwp zkM;}}&L5i$bxbgKKHL4_DBK^>=ZEfo^uk-;zrViYtBcQdpIPazUb@M4o2UV zyDxnFET7E(;a(aLp!HVuUXpp^t{Ep+SfgPU~B)pPj@|X zwk`DK>J?1K{vE-#Wvd^IeX*O|{$29Y07Op|9@#PE+O?#wXJ6;kTD-d+)JR UXL=U)Ie&tU_x42(t=as{e=9bQEdT%j literal 0 HcmV?d00001 diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index 418e623aa8..8a266198a5 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -31,7 +31,7 @@ function showSetupComplete() { snapshotImagesDiv.innerHTML = 'Snapshot Instructions' + '
' + "

You're all set!

" + - '

Try taking a snapshot by pressing the button below.

'; + '

Try taking a snapshot by pressing the red button below.

'; } function chooseSnapshotLocation() { EventBridge.emitWebEvent(JSON.stringify({ @@ -54,25 +54,30 @@ function addImage(image_data, isGifLoading, isShowingPreviousImages, canSharePre if (!image_data.localPath) { return; } - var div = document.createElement("DIV"); var id = "p" + idCounter++; + // imageContainer setup + var imageContainer = document.createElement("DIV"); + imageContainer.id = id; + imageContainer.style.width = "100%"; + imageContainer.style.height = "251px"; + imageContainer.style.display = "flex"; + imageContainer.style.justifyContent = "center"; + imageContainer.style.alignItems = "center"; + imageContainer.style.position = "relative"; + // img setup var img = document.createElement("IMG"); - div.id = id; img.id = id + "img"; - div.style.width = "100%"; - div.style.height = "" + 502 / imageCount + "px"; - div.style.display = "flex"; - div.style.justifyContent = "center"; - div.style.alignItems = "center"; - div.style.position = "relative"; if (imageCount > 1) { img.setAttribute("class", "multiple"); } img.src = image_data.localPath; - div.appendChild(img); - document.getElementById("snapshot-images").appendChild(div); - var isGif = img.src.split('.').pop().toLowerCase() === "gif"; + imageContainer.appendChild(img); + document.getElementById("snapshot-images").appendChild(imageContainer); paths.push(image_data.localPath); + var isGif = img.src.split('.').pop().toLowerCase() === "gif"; + if (isGif) { + imageContainer.innerHTML += 'GIF'; + } if (!isGifLoading && !isShowingPreviousImages) { shareForUrl(id); } else if (isShowingPreviousImages && canSharePreviousImages) { @@ -83,92 +88,68 @@ function appendShareBar(divID, story_id, isGif, hifiShareButtonsDisabled) { var story_url = "https://highfidelity.com/user_stories/" + story_id; var parentDiv = document.getElementById(divID); parentDiv.setAttribute('data-story-id', story_id); - document.getElementById(divID).appendChild(createShareOverlay(divID, isGif, story_url, hifiShareButtonsDisabled)); + document.getElementById(divID).appendChild(createShareBar(divID, isGif, story_url, hifiShareButtonsDisabled)); } -function createShareOverlay(parentID, isGif, shareURL, hifiShareButtonsDisabled) { - var shareOverlayContainer = document.createElement("DIV"); - shareOverlayContainer.id = parentID + "shareOverlayContainer"; - shareOverlayContainer.style.position = "absolute"; - shareOverlayContainer.style.top = "0px"; - shareOverlayContainer.style.left = "0px"; - shareOverlayContainer.style.display = "flex"; - shareOverlayContainer.style.alignItems = "flex-end"; - shareOverlayContainer.style.width = "100%"; - shareOverlayContainer.style.height = "100%"; - +function createShareBar(parentID, isGif, shareURL, hifiShareButtonsDisabled) { var shareBar = document.createElement("div"); - shareBar.id = parentID + "shareBar" - shareBar.style.display = "inline"; - shareBar.style.width = "100%"; - shareBar.style.height = "60px"; - shareBar.style.lineHeight = "60px"; - shareBar.style.clear = "both"; - shareBar.style.marginLeft = "auto"; - shareBar.style.marginRight = "auto"; - shareBar.innerHTML = isGif ? 'GIF' : ""; - var shareButtonID = parentID + "shareButton"; - shareBar.innerHTML += '
' + - '' + - '' + - '
' - shareOverlayContainer.appendChild(shareBar); - - var shareOverlayBackground = document.createElement("div"); - shareOverlayBackground.id = parentID + "shareOverlayBackground"; - shareOverlayBackground.style.display = "none"; - shareOverlayBackground.style.position = "absolute"; - shareOverlayBackground.style.zIndex = "1"; - shareOverlayBackground.style.top = "0px"; - shareOverlayBackground.style.left = "0px"; - shareOverlayBackground.style.backgroundColor = "black"; - shareOverlayBackground.style.opacity = "0.5"; - shareOverlayBackground.style.width = "100%"; - shareOverlayBackground.style.height = "100%"; - shareOverlayContainer.appendChild(shareOverlayBackground); - - var shareOverlay = document.createElement("div"); - shareOverlay.id = parentID + "shareOverlay"; - shareOverlay.className = "shareOverlayDiv"; - shareOverlay.style.display = "none"; - shareOverlay.style.width = "100%"; - shareOverlay.style.height = "100%"; - shareOverlay.style.zIndex = "2"; + shareBar.id = parentID + "shareBar"; + shareBar.className = "shareControls"; + var shareButtonsDivID = parentID + "shareButtonsDiv"; + var showShareButtonsButtonDivID = parentID + "showShareButtonsButtonDiv"; + var showShareButtonsButtonID = parentID + "showShareButtonsButton"; + var showShareButtonsLabelID = parentID + "showShareButtonsLabel"; + var blastToConnectionsButtonID = parentID + "blastToConnectionsButton"; var shareWithEveryoneButtonID = parentID + "shareWithEveryoneButton"; - var inviteConnectionsCheckboxID = parentID + "inviteConnectionsCheckbox"; var facebookButtonID = parentID + "facebookButton"; var twitterButtonID = parentID + "twitterButton"; - shareOverlay.innerHTML = '' + - '
' + - '
' + - '
' + - '
' + - '' + - '
' + - '' + - '
' + - '
' + - '' + - '' + + shareBar.innerHTML += '' + + '' + + '
' + + '' + + '' + + '
' + + '' + '
' + '
'; - shareOverlayContainer.appendChild(shareOverlay); - return shareOverlayContainer; + return shareBar; } -function selectImageToShare(selectedID) { - selectedID = selectedID.id; // `selectedID` is passed as an HTML object to these functions; we just want the ID - var shareOverlayContainer = document.getElementById(selectedID + "shareOverlayContainer"); +function selectImageToShare(selectedID, isSelected) { + if (selectedID.id) { + selectedID = selectedID.id; // sometimes (?), `selectedID` is passed as an HTML object to these functions; we just want the ID + } + var imageContainer = document.getElementById(selectedID); var shareBar = document.getElementById(selectedID + "shareBar"); - var shareOverlayBackground = document.getElementById(selectedID + "shareOverlayBackground"); - var shareOverlay = document.getElementById(selectedID + "shareOverlay"); + var shareButtonsDiv = document.getElementById(selectedID + "shareButtonsDiv"); + var showShareButtonsButton = document.getElementById(selectedID + "showShareButtonsButton"); - shareOverlay.style.outline = "4px solid #00b4ef"; - shareOverlay.style.outlineOffset = "-4px"; + if (isSelected) { + showShareButtonsButton.onclick = function () { selectImageToShare(selectedID, false) }; + showShareButtonsButton.classList.remove("inactive"); + showShareButtonsButton.classList.add("active"); - shareBar.style.display = "none"; + imageContainer.style.outline = "4px solid #00b4ef"; + imageContainer.style.outlineOffset = "-4px"; - shareOverlayBackground.style.display = "inline"; - shareOverlay.style.display = "inline"; + shareBar.style.backgroundColor = "rgba(0, 0, 0, 0.5)"; + + shareButtonsDiv.style.opacity = "1.0"; + } else { + showShareButtonsButton.onclick = function () { selectImageToShare(selectedID, true) }; + showShareButtonsButton.classList.remove("active"); + showShareButtonsButton.classList.add("inactive"); + + imageContainer.style.outline = "none"; + + shareBar.style.backgroundColor = "rgba(0, 0, 0, 0.0)"; + + shareButtonsDiv.style.opacity = "0.0"; + } } function shareForUrl(selectedID) { EventBridge.emitWebEvent(JSON.stringify({ @@ -177,17 +158,29 @@ function shareForUrl(selectedID) { data: paths[parseInt(selectedID.substring(1))] })); } +function blastToConnections(selectedID, isGif) { + selectedID = selectedID.id; // `selectedID` is passed as an HTML object to these functions; we just want the ID + + document.getElementById(selectedID + "blastToConnectionsButton").setAttribute("disabled", "disabled"); + document.getElementById(selectedID + "shareWithEveryoneButton").setAttribute("disabled", "disabled"); + + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "blastToConnections", + story_id: document.getElementById(selectedID).getAttribute("data-story-id"), + isGif: isGif + })); +} function shareWithEveryone(selectedID, isGif) { selectedID = selectedID.id; // `selectedID` is passed as an HTML object to these functions; we just want the ID + document.getElementById(selectedID + "blastToConnectionsButton").setAttribute("disabled", "disabled"); document.getElementById(selectedID + "shareWithEveryoneButton").setAttribute("disabled", "disabled"); - document.getElementById(selectedID + "inviteConnectionsCheckbox").setAttribute("disabled", "disabled"); EventBridge.emitWebEvent(JSON.stringify({ type: "snapshot", action: "shareSnapshotWithEveryone", story_id: document.getElementById(selectedID).getAttribute("data-story-id"), - isAnnouncement: document.getElementById(selectedID + "inviteConnectionsCheckbox").getAttribute("checked"), isGif: isGif })); } @@ -201,17 +194,9 @@ function shareButtonClicked(selectedID) { } function cancelSharing(selectedID) { selectedID = selectedID.id; // `selectedID` is passed as an HTML object to these functions; we just want the ID - var shareOverlayContainer = document.getElementById(selectedID + "shareOverlayContainer"); var shareBar = document.getElementById(selectedID + "shareBar"); - var shareOverlayBackground = document.getElementById(selectedID + "shareOverlayBackground"); - var shareOverlay = document.getElementById(selectedID + "shareOverlay"); - - shareOverlay.style.outline = "none"; shareBar.style.display = "inline"; - - shareOverlayBackground.style.display = "none"; - shareOverlay.style.display = "none"; } function handleCaptureSetting(setting) { @@ -331,6 +316,6 @@ function testInBrowser(isTestingSetupInstructions) { } else { imageCount = 1; //addImage({ localPath: 'http://lorempixel.com/553/255' }); - addImage({ localPath: 'C:/Users/valef/Desktop/hifi-snap-by-zfox-on-2017-04-26_10-26-53.jpg' }, false, true, true, false); + addImage({ localPath: 'C:/Users/valef/Desktop/hifi-snap-by-zfox-on-2017-04-26_10-26-53.gif' }, false, true, true, false); } } diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 06d519969e..9ec81eec10 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -85,6 +85,16 @@ function request(options, callback) { // cb(error, responseOfCorrectContentType) httpRequest.send(options.body); } +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 onMessage(message) { // Receives message from the html dialog via the qwebchannel EventBridge. This is complicated by the following: // 1. Although we can send POJOs, we cannot receive a toplevel object. (Arrays of POJOs are fine, though.) @@ -97,7 +107,6 @@ function onMessage(message) { } var isLoggedIn; - var needsLogin = false; switch (message.action) { case 'ready': // DOM is ready and page has loaded tablet.emitScriptEvent(JSON.stringify({ @@ -138,7 +147,8 @@ function onMessage(message) { } break; case 'openSettings': - if ((HMD.active && Settings.getValue("hmdTabletBecomesToolbar", true)) || (!HMD.active && Settings.getValue("desktopTabletBecomesToolbar", false))) { + if ((HMD.active && Settings.getValue("hmdTabletBecomesToolbar", false)) + || (!HMD.active && Settings.getValue("desktopTabletBecomesToolbar", true))) { Desktop.show("hifi/dialogs/GeneralPreferencesDialog.qml", "General Preferences"); } else { tablet.loadQMLOnTop("TabletGeneralPreferences.qml"); @@ -164,6 +174,64 @@ function onMessage(message) { // TODO } break; + case 'blastToConnections': + isLoggedIn = Account.isLoggedIn(); + storyIDsToMaybeDelete.splice(storyIDsToMaybeDelete.indexOf(message.story_id), 1); + if (message.isGif) { + Settings.setValue("previousAnimatedSnapSharingDisabled", true); + } else { + Settings.setValue("previousStillSnapSharingDisabled", true); + } + + if (isLoggedIn) { + print('Uploading new story for announcement!'); + + request({ + uri: METAVERSE_BASE + '/api/v1/user_stories/' + message.story_id, + method: 'GET' + }, function (error, response) { + if (error || (response.status !== 'success')) { + print("ERROR getting details about existing snapshot story:", error || response.status); + return; + } else { + var requestBody = { + user_story: { + audience: "for_feed", + action: "announcement", + path: response.user_story.path, + place_name: response.user_story.place_name, + thumbnail_url: response.user_story.thumbnail_url, + details: { + shareable_url: response.user_story.details.shareable_url, + image_url: response.user_story.details.image_url + } + } + } + request({ + uri: METAVERSE_BASE + '/api/v1/user_stories', + method: 'POST', + json: true, + body: requestBody + }, function (error, response) { + if (error || (response.status !== 'success')) { + print("ERROR uploading announcement story: ", error || response.status); + if (message.isGif) { + Settings.setValue("previousAnimatedSnapSharingDisabled", false); + } else { + Settings.setValue("previousStillSnapSharingDisabled", false); + } + return; + } else { + print("SUCCESS uploading announcement story! Story ID:", response.user_story.id); + } + }); + } + }); + + } else { + openLoginWindow(); + } + break; case 'shareSnapshotWithEveryone': isLoggedIn = Account.isLoggedIn(); storyIDsToMaybeDelete.splice(storyIDsToMaybeDelete.indexOf(message.story_id), 1); @@ -202,19 +270,10 @@ function onMessage(message) { } }); } else { - needsLogin = true; + openLoginWindow(); shareAfterLogin = true; snapshotToShareAfterLogin = { path: message.data, href: message.href || href }; } - if (needsLogin) { - if ((HMD.active && Settings.getValue("hmdTabletBecomesToolbar")) - || (!HMD.active && Settings.getValue("desktopTabletBecomesToolbar"))) { - Menu.triggerOption("Login / Sign Up"); - } else { - tablet.loadQMLOnTop("../../dialogs/TabletLoginDialog.qml"); - HMD.openTablet(); - } - } break; case 'shareButtonClicked': print('Twitter or FB "Share" button clicked! Removing ID', message.story_id, 'from storyIDsToMaybeDelete[].'); From b6601feb1d20fe7e19630ff3a438b224eef280ed Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 26 Apr 2017 16:50:11 -0700 Subject: [PATCH 66/76] It's 5 already? --- scripts/system/html/css/SnapshotReview.css | 36 +++++---------------- scripts/system/html/css/hifi-style.css | 36 ++++++++++++++++++++- scripts/system/html/img/shareToFeed.png | Bin 0 -> 15486 bytes scripts/system/html/js/SnapshotReview.js | 4 +-- 4 files changed, 45 insertions(+), 31 deletions(-) create mode 100644 scripts/system/html/img/shareToFeed.png diff --git a/scripts/system/html/css/SnapshotReview.css b/scripts/system/html/css/SnapshotReview.css index fdfc3bfca9..9258aa7f1a 100644 --- a/scripts/system/html/css/SnapshotReview.css +++ b/scripts/system/html/css/SnapshotReview.css @@ -126,9 +126,13 @@ input[type=button].naked:active { height: 29px; } .shareWithEveryone { + background: #DDDDDD url(../img/shareToFeed.png) no-repeat scroll center; + border-width: 0px; text-align: left; margin-right: 8px; height: 29px; + width: 30px; + border-radius: 3px; } .facebookButton { background-image: url(../img/fb_logo.png); @@ -143,6 +147,7 @@ input[type=button].naked:active { height: 29px; display: inline-block; margin-right: 8px; + border-radius: 3px; } .showShareButtonsButtonDiv { display: inline-flex; @@ -188,6 +193,7 @@ input[type=button].naked:active { width: 32px; height: 40px; position: absolute; + top: 5px; right: 14px; pointer-events: none; } @@ -226,8 +232,8 @@ input[type=button].naked:active { } #snap-button { - width: 65px; - height: 65px; + width: 75px; + height: 75px; padding: 0; border-radius: 50%; background: #EA4C5F; @@ -273,32 +279,6 @@ h4 { margin: 0 auto; display: block; } - -.prompt { - font-family: Raleway-SemiBold; - font-size: 14px; -} - -.compound-button { - position: relative; - height: auto; -} - -.compound-button input { - padding-left: 40px; -} - -.compound-button { - display: inline-block; - position: absolute; - left: 12px; - top: 16px; - width: 23px; - height: 23px; - background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgaGVpZ2h0PSI0MCIKICAgd2lkdGg9IjQwIgogICBpZD0ic3ZnMiIKICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIKICAgdmlld0JveD0iMCAwIDQwIDQwIgogICB5PSIwcHgiCiAgIHg9IjBweCIKICAgdmVyc2lvbj0iMS4xIj48bWV0YWRhdGEKICAgICBpZD0ibWV0YWRhdGEzNCI+PHJkZjpSREY+PGNjOldvcmsKICAgICAgICAgcmRmOmFib3V0PSIiPjxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0PjxkYzp0eXBlCiAgICAgICAgICAgcmRmOnJlc291cmNlPSJodHRwOi8vcHVybC5vcmcvZGMvZGNtaXR5cGUvU3RpbGxJbWFnZSIgLz48ZGM6dGl0bGU+PC9kYzp0aXRsZT48L2NjOldvcms+PC9yZGY6UkRGPjwvbWV0YWRhdGE+PGRlZnMKICAgICBpZD0iZGVmczMyIiAvPjxzdHlsZQogICAgIGlkPSJzdHlsZTQiCiAgICAgdHlwZT0idGV4dC9jc3MiPgoJLnN0MHtmaWxsOiM0MTQwNDI7fQoJLnN0MXtmaWxsOiNDQ0NDQ0M7fQoJLnN0MntmaWxsOiMxMzk4QkI7fQoJLnN0M3tmaWxsOiMzMUQ4RkY7fQo8L3N0eWxlPjxnCiAgICAgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwtMTEwKSIKICAgICBpZD0iTGF5ZXJfMSI+PGNpcmNsZQogICAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZjtmaWxsLW9wYWNpdHk6MSIKICAgICAgIGlkPSJjaXJjbGUxMyIKICAgICAgIHI9IjQuNDQwMDAwMSIKICAgICAgIGN5PSIxMjYuMTciCiAgICAgICBjeD0iMjAuNTQwMDAxIgogICAgICAgY2xhc3M9InN0MSIgLz48cGF0aAogICAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZjtmaWxsLW9wYWNpdHk6MSIKICAgICAgIGlkPSJwYXRoMTUiCiAgICAgICBkPSJtIDI4Ljg3LDEzOS4yNiBjIDAuMDEsLTAuMDEgMC4wMiwtMC4wMiAwLjAzLC0wLjAzIGwgMCwtMS44NiBjIDAsLTIuNjggLTIuMzMsLTQuNzcgLTUsLTQuNzcgbCAtNi40MiwwIGMgLTIuNjgsMCAtNC44NSwyLjA5IC00Ljg1LDQuNzcgbCAwLDEuODggMTYuMjQsMCB6IgogICAgICAgY2xhc3M9InN0MSIgLz48cGF0aAogICAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZjtmaWxsLW9wYWNpdHk6MSIKICAgICAgIGlkPSJwYXRoMTciCiAgICAgICBkPSJtIDM4LjE3LDEyMy40MiBjIDAsLTMuOTcgLTMuMjIsLTcuMTkgLTcuMTksLTcuMTkgbCAtMjAuMzEsMCBjIC0zLjk3LDAgLTcuMTksMy4yMiAtNy4xOSw3LjE5IGwgMCwxNC4xOCBjIDAsMy45NyAzLjIyLDcuMTkgNy4xOSw3LjE5IGwgMjAuMzEsMCBjIDMuOTcsMCA3LjE5LC0zLjIyIDcuMTksLTcuMTkgbCAwLC0xNC4xOCB6IG0gLTEuNzgsMTQuMjcgYyAwLDMuMDMgLTIuNDYsNS40OSAtNS40OSw1LjQ5IGwgLTIwLjMyLDAgYyAtMy4wMywwIC01LjQ5LC0yLjQ2IC01LjQ5LC01LjQ5IGwgMCwtMTQuMTkgYyAwLC0zLjAzIDIuNDYsLTUuNDkgNS40OSwtNS40OSBsIDIwLjMzLDAgYyAzLjAzLDAgNS40OSwyLjQ2IDUuNDksNS40OSBsIDAsMTQuMTkgeiIKICAgICAgIGNsYXNzPSJzdDEiIC8+PC9nPjxnCiAgICAgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwtMTEwKSIKICAgICBpZD0iTGF5ZXJfMiIgLz48L3N2Zz4=); - background-repeat: no-repeat; - background-size: 23px 23px; -} /* // END misc styling */ diff --git a/scripts/system/html/css/hifi-style.css b/scripts/system/html/css/hifi-style.css index 41cda569c9..6e8dd7c710 100644 --- a/scripts/system/html/css/hifi-style.css +++ b/scripts/system/html/css/hifi-style.css @@ -127,8 +127,42 @@ input[type=radio]:active + label > span > span{ display: block; width: 10px; height: 10px; - margin: 2.5px; + margin: 3px; border: 2px solid #36CDFF; border-radius: 50%; background: #00B4EF; +} + +.grayButton { + font-family: FiraSans-SemiBold; + color: white; + padding: 0px 10px; + border-width: 0px; + background-image: linear-gradient(#FFFFFF, #AFAFAF); +} +.grayButton:hover { + background-image: linear-gradient(#FFFFFF, #FFFFFF); +} +.grayButton:active { + background-image: linear-gradient(#AFAFAF, #AFAFAF); +} +.grayButton:disabled { + background-image: linear-gradient(#FFFFFF, ##AFAFAF); +} +.blueButton { + font-family: FiraSans-SemiBold; + color: white; + padding: 0px 10px; + border-radius: 3px; + border-width: 0px; + background-image: linear-gradient(#00B4EF, #1080B8); +} +.blueButton:hover { + background-image: linear-gradient(#00B4EF, #00B4EF); +} +.blueButton:active { + background-image: linear-gradient(#1080B8, #1080B8); +} +.blueButton:disabled { + background-image: linear-gradient(#FFFFFF, ##AFAFAF); } \ No newline at end of file diff --git a/scripts/system/html/img/shareToFeed.png b/scripts/system/html/img/shareToFeed.png new file mode 100644 index 0000000000000000000000000000000000000000..f681c49d8f83a3f77ec8db52f62ff2bd1e6ee628 GIT binary patch literal 15486 zcmeI3ZEO_B8OH}h;@XV~#K_=)(De}10rvLg-n^^3a~z+I-E!tIZn0Y+-rJqCx4zrm z?yh}j%OR&yC{C=pA*!IHp$$@6wIz{h(S%v{3E6 z_+D%iMo~UQnxABUn3@0c?EL3>X79@m2AfxvmEK(n04Qr}^tS*2EC7H-U%DLt0FFFw z1pt(&jqN%BUoJQQW`Ny4T>t?4rqtSIv;|gkf)cgyq7s6(cvLk-z*7@fd7&E`mJsZe zWUuvK2mfrfNTS!e-Wi|*Y8~v78v8Z4uD`ie=zp}ES}n;XMz_~mYbLa`1%j43MS~Wn%|!^bz1retZM2iEW*jRm3`M(0iX~|W zLD3w=aujVzWA&9FG&T-#PLI2&?ME66xm|NPn9$!1`V&8%w5o2vL}bqxNW#q(7oS!U&8) z+0aSaMo;+&YL{XtdY3ZIz9*)q-Mt}&vxk%CnK?79492BtWadmtmMn!7o_JK{>NLn3 ziq@(q5nrmxf|;kc)YX|)<+fCGycZLP_?#v(ccRH=xf{5|o2rT&=W=bGQh~ z%~4cNgw(Qv00C(5B5&}Y8X|9DVGu=>!hN3}BD*X&J8QJHTe^L~J`!6*BY_9Wj>d1Hm^+sr-om$?!4hYz4zPD)W_@+Vwh>7ogAOblRPs3q&0;TQwrO&LZ&)bm@|FHQM5D@RfSViC>r*&F4#Y zN1r1&2@=>YB(PmbV7rjOb|Hc7LIT@`1hxwaY!?#PE+nvBNMO5=z;+>l?Lq?Eg#@+> z32YY<*e)cnT}WWNkid2!f$c&9+l2(S3khr&64)*zuw6)CyO6+kA%X2e0^5ZIwhIYt z7ZTVmB(PmbV7rjOb|Hc7LIT@`#7*rg&3>~9WzlPmS!;YN7oKi3KL-^WTLJ*|F9jg6 z1%PWe07$$GKo14LdmR99y8xK4?706}0{}$}oBXw{@z>u!>+Nc-om2epw{BZobZz+qU??!Ptvi9hLDn4!!pHPi~w%aXohb6B}&bUL);4_1hU8>%Mu$`H|;= zHy`T0cyw{(#dl*Rk1lv&&gS*YF8pWBg;V#IyhSJeS2k;Y_nl*|UmRUux3r3%_jurM z+N!I|-??q1Y&f|6<6lu9+_CZN``9gqvFX`jl?@(sFFLYe ztgF~IR@A#~^Xh+!byN3__aOiqHW4kcpN4=j6gr82la(CI;-5)jASM0cS;pMx| zghrp)J#b=Hge$jo6fLQB-E(Q^e0yR8TmH%;57stsxjJ<2^n=^azqCJm5-h5%neF$k zsC@s}(t(oq9(cY2%pT{%JH;m#eo}PevGA3@$akDA*&~m?J<>im@#Kj^^Y@*7>E1u| zA8FXOtKu@)wfguAV;}#wboLVuogDPLAN^(V@$ssy2M_erMATX;^=I;|6j0zW$m!6R%wyJM;6G<)NoOY!1%;+3)&dhZmf3e(g8T zS1(6?U$eG*p>I&QviQ)LGPCx^`SK6GKm6;;m63s6``n2eGr``uQv0)ehPRnt2shO? L`=7qA<9q)H#YG)p literal 0 HcmV?d00001 diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index 8a266198a5..eb28aa4be5 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -104,8 +104,8 @@ function createShareBar(parentID, isGif, shareURL, hifiShareButtonsDisabled) { var twitterButtonID = parentID + "twitterButton"; shareBar.innerHTML += '' + '' + From 0ac660d4ab5872c30549ffcd031c8af888cecf59 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 26 Apr 2017 16:55:20 -0700 Subject: [PATCH 67/76] Cleanup --- scripts/system/html/css/SnapshotReview.css | 2 +- scripts/system/snapshot.js | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/system/html/css/SnapshotReview.css b/scripts/system/html/css/SnapshotReview.css index 9258aa7f1a..d86befc30a 100644 --- a/scripts/system/html/css/SnapshotReview.css +++ b/scripts/system/html/css/SnapshotReview.css @@ -117,7 +117,7 @@ input[type=button].naked:active { .shareButtons { display: flex; align-items: center; - margin-left: 40px; + margin-left: 30px; height: 100%; } .blastToConnections { diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 9ec81eec10..ecb7aba982 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -36,6 +36,8 @@ var shareAfterLogin = false; var snapshotToShareAfterLogin; var METAVERSE_BASE = location.metaverseServerUrl; +// It's totally unnecessary to return to C++ to perform many of these requests, such as DELETEing an old story, +// POSTING a new one, PUTTING a new audience, or GETTING story data. It's far more efficient to do all of that within JS function request(options, callback) { // cb(error, responseOfCorrectContentType) of url. A subset of npm request. var httpRequest = new XMLHttpRequest(), key; // QT bug: apparently doesn't handle onload. Workaround using readyState. From 7297355183b7fccbdc40f796b40e4012ba08532e Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 26 Apr 2017 17:16:01 -0700 Subject: [PATCH 68/76] Open ShareBar on P0 by default --- scripts/system/html/SnapshotReview.html | 2 +- scripts/system/html/js/SnapshotReview.js | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/system/html/SnapshotReview.html b/scripts/system/html/SnapshotReview.html index 9469a9d313..fb40c04d05 100644 --- a/scripts/system/html/SnapshotReview.html +++ b/scripts/system/html/SnapshotReview.html @@ -9,7 +9,7 @@
- +
diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index eb28aa4be5..8a204840ac 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -89,6 +89,9 @@ function appendShareBar(divID, story_id, isGif, hifiShareButtonsDisabled) { var parentDiv = document.getElementById(divID); parentDiv.setAttribute('data-story-id', story_id); document.getElementById(divID).appendChild(createShareBar(divID, isGif, story_url, hifiShareButtonsDisabled)); + if (divID === "p0") { + selectImageToShare(divID, true); + } } function createShareBar(parentID, isGif, shareURL, hifiShareButtonsDisabled) { var shareBar = document.createElement("div"); From 832e16ef8644bcc9b29735f52eb318040a00480d Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 27 Apr 2017 11:18:23 -0700 Subject: [PATCH 69/76] Visual tweaks; fix active share buttons; comment out tests --- scripts/system/html/css/SnapshotReview.css | 13 ++++++++----- scripts/system/html/css/hifi-style.css | 2 ++ scripts/system/html/js/SnapshotReview.js | 14 +++++++++----- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/scripts/system/html/css/SnapshotReview.css b/scripts/system/html/css/SnapshotReview.css index d86befc30a..12b91d372b 100644 --- a/scripts/system/html/css/SnapshotReview.css +++ b/scripts/system/html/css/SnapshotReview.css @@ -60,6 +60,8 @@ input[type=button].naked:active { .snapshotInstructions { font-family: Raleway-Regular; margin: 0 20px; + width: 100%; + height: 50%; } /* // END styling of snapshot instructions panel @@ -119,6 +121,7 @@ input[type=button].naked:active { align-items: center; margin-left: 30px; height: 100%; + width: 80%; } .blastToConnections { text-align: left; @@ -158,6 +161,7 @@ input[type=button].naked:active { text-shadow: 2px 2px 3px #000000; height: 100%; margin-right: 10px; + width: 20%; } .showShareButton { width: 40px; @@ -217,7 +221,6 @@ input[type=button].naked:active { width: 100%; position: absolute; left: 0; - margin-top: 8px; overflow: hidden; display: flex; justify-content: center; @@ -225,20 +228,20 @@ input[type=button].naked:active { #snap-settings { display: inline; width: 150px; - margin: auto; + margin: 2px auto 0 auto; } #snap-settings form input { margin-bottom: 5px; } #snap-button { - width: 75px; - height: 75px; + width: 72px; + height: 72px; padding: 0; border-radius: 50%; background: #EA4C5F; border: 3px solid white; - margin: auto; + margin: 2px auto 0 auto; box-sizing: content-box; display: inline; outline:none; diff --git a/scripts/system/html/css/hifi-style.css b/scripts/system/html/css/hifi-style.css index 6e8dd7c710..37810707e0 100644 --- a/scripts/system/html/css/hifi-style.css +++ b/scripts/system/html/css/hifi-style.css @@ -156,6 +156,8 @@ input[type=radio]:active + label > span > span{ border-radius: 3px; border-width: 0px; background-image: linear-gradient(#00B4EF, #1080B8); + min-height: 30px; + } .blueButton:hover { background-image: linear-gradient(#00B4EF, #00B4EF); diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index 8a204840ac..f5aaa59d90 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -22,7 +22,9 @@ function showSetupInstructions() { "

Setup Instructions

" + "

Before you can begin taking snaps, please choose where you'd like to save snaps on your computer:

" + '
' + - ''; + '
' + + '' + + '
'; document.getElementById("snap-button").disabled = true; } function showSetupComplete() { @@ -106,7 +108,7 @@ function createShareBar(parentID, isGif, shareURL, hifiShareButtonsDisabled) { var facebookButtonID = parentID + "facebookButton"; var twitterButtonID = parentID + "twitterButton"; shareBar.innerHTML += '' + - '