overte/interface/src/ui/Keyboard.cpp

965 lines
37 KiB
C++

//
// Keyboard.cpp
// interface/src/scripting
//
// Created by Dante Ruiz on 2018-08-27.
// Copyright 2018 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "Keyboard.h"
#include <utility>
#include <glm/gtc/quaternion.hpp>
#include <glm/gtx/quaternion.hpp>
#include <QtGui/qevent.h>
#include <QFile>
#include <QTextStream>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonParseError>
#include <PointerEvent.h>
#include <PointerManager.h>
#include <Pick.h>
#include <controllers/UserInputMapper.h>
#include <PathUtils.h>
#include <ResourceManager.h>
#include <SoundCache.h>
#include <AudioInjector.h>
#include <RegisteredMetaTypes.h>
#include <ui/TabletScriptingInterface.h>
#include "avatar/AvatarManager.h"
#include "avatar/MyAvatar.h"
#include "avatar/AvatarManager.h"
#include "raypick/PickScriptingInterface.h"
#include "scripting/HMDScriptingInterface.h"
#include "scripting/WindowScriptingInterface.h"
#include "scripting/SelectionScriptingInterface.h"
#include "scripting/HMDScriptingInterface.h"
#include "DependencyManager.h"
#include "raypick/StylusPointer.h"
#include "GLMHelpers.h"
#include "Application.h"
static const int LEFT_HAND_CONTROLLER_INDEX = 0;
static const int RIGHT_HAND_CONTROLLER_INDEX = 1;
static const float MALLET_LENGTH = 0.18f;
static const float MALLET_TOUCH_Y_OFFSET = 0.050f;
static const float MALLET_Y_OFFSET = 0.160f;
static const glm::quat MALLET_ROTATION_OFFSET{0.70710678f, 0.0f, -0.70710678f, 0.0f};
static const glm::vec3 MALLET_MODEL_DIMENSIONS{0.01f, MALLET_LENGTH, 0.01f};
static const glm::vec3 MALLET_POSITION_OFFSET{0.0f, -MALLET_Y_OFFSET / 2.0f, 0.0f};
static const glm::vec3 MALLET_TIP_OFFSET{0.0f, MALLET_LENGTH - MALLET_TOUCH_Y_OFFSET, 0.0f};
static const glm::vec3 Z_AXIS {0.0f, 0.0f, 1.0f};
static const glm::vec3 KEYBOARD_TABLET_OFFSET{0.30f, -0.38f, -0.04f};
static const glm::vec3 KEYBOARD_TABLET_DEGREES_OFFSET{-45.0f, 0.0f, 0.0f};
static const glm::vec3 KEYBOARD_TABLET_LANDSCAPE_OFFSET{-0.2f, -0.27f, -0.05f};
static const glm::vec3 KEYBOARD_TABLET_LANDSCAPE_DEGREES_OFFSET{-45.0f, 0.0f, -90.0f};
static const glm::vec3 KEYBOARD_AVATAR_OFFSET{-0.6f, 0.3f, -0.7f};
static const glm::vec3 KEYBOARD_AVATAR_DEGREES_OFFSET{0.0f, 180.0f, 0.0f};
static const QString SOUND_FILE = PathUtils::resourcesUrl() + "sounds/keyboardPress.mp3";
static const QString MALLET_MODEL_URL = PathUtils::resourcesUrl() + "meshes/drumstick.fbx";
static const float PULSE_STRENGTH = 0.6f;
static const float PULSE_DURATION = 3.0f;
static const int KEY_PRESS_TIMEOUT_MS = 100;
static const int LAYER_SWITCH_TIMEOUT_MS = 200;
static const QString CHARACTER_STRING = "character";
static const QString CAPS_STRING = "caps";
static const QString CLOSE_STRING = "close";
static const QString LAYER_STRING = "layer";
static const QString BACKSPACE_STRING = "backspace";
static const QString SPACE_STRING = "space";
static const QString ENTER_STRING = "enter";
static const QString KEY_HOVER_HIGHLIGHT = "keyHoverHiglight";
static const QString KEY_PRESSED_HIGHLIGHT = "keyPressesHighlight";
static const QVariantMap KEY_HOVERING_STYLE {
{ "isOutlineSmooth", true },
{ "outlineWidth", 3 },
{ "outlineUnoccludedColor", QVariantMap {{"red", 13}, {"green", 152}, {"blue", 186}}},
{ "outlineUnoccludedAlpha", 1.0 },
{ "outlineOccludedAlpha", 0.0 },
{ "fillUnoccludedAlpha", 0.0 },
{ "fillOccludedAlpha", 0.0 }
};
static const QVariantMap KEY_PRESSING_STYLE {
{ "isOutlineSmooth", true },
{ "outlineWidth", 3 },
{ "fillUnoccludedColor", QVariantMap {{"red", 50}, {"green", 50}, {"blue", 50}}},
{ "outlineUnoccludedAlpha", 0.0 },
{ "outlineOccludedAlpha", 0.0 },
{ "fillUnoccludedAlpha", 0.6 },
{ "fillOccludedAlpha", 0.0 }
};
std::pair<glm::vec3, glm::quat> calculateKeyboardPositionAndOrientation() {
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
auto hmd = DependencyManager::get<HMDScriptingInterface>();
std::pair<glm::vec3, glm::quat> keyboardLocation = std::make_pair(glm::vec3(), glm::quat());
float sensorToWorldScale = myAvatar->getSensorToWorldScale();
QUuid tabletID = hmd->getCurrentTabletFrameID();
if (!tabletID.isNull() && hmd->getShouldShowTablet()) {
EntityPropertyFlags desiredProperties;
desiredProperties += PROP_POSITION;
desiredProperties += PROP_ROTATION;
auto properties = DependencyManager::get<EntityScriptingInterface>()->getEntityProperties(tabletID, desiredProperties);
auto tablet = DependencyManager::get<TabletScriptingInterface>()->getTablet("com.highfidelity.interface.tablet.system");
bool landscapeMode = tablet->getLandscape();
glm::vec3 keyboardOffset = landscapeMode ? KEYBOARD_TABLET_LANDSCAPE_OFFSET : KEYBOARD_TABLET_OFFSET;
glm::vec3 keyboardDegreesOffset = landscapeMode ? KEYBOARD_TABLET_LANDSCAPE_DEGREES_OFFSET : KEYBOARD_TABLET_DEGREES_OFFSET;
glm::vec3 tabletWorldPosition = properties.getPosition();
glm::quat tabletWorldOrientation = properties.getRotation();
glm::vec3 scaledKeyboardTabletOffset = keyboardOffset * sensorToWorldScale;
keyboardLocation.first = tabletWorldPosition + (tabletWorldOrientation * scaledKeyboardTabletOffset);
keyboardLocation.second = tabletWorldOrientation * glm::quat(glm::radians(keyboardDegreesOffset));
} else {
glm::vec3 avatarWorldPosition = myAvatar->getWorldPosition();
glm::quat avatarWorldOrientation = myAvatar->getWorldOrientation();
glm::vec3 scaledKeyboardAvatarOffset = KEYBOARD_AVATAR_OFFSET * sensorToWorldScale;
keyboardLocation.first = avatarWorldPosition + (avatarWorldOrientation * scaledKeyboardAvatarOffset);
keyboardLocation.second = avatarWorldOrientation * glm::quat(glm::radians(KEYBOARD_AVATAR_DEGREES_OFFSET));
}
return keyboardLocation;
}
void Key::saveDimensionsAndLocalPosition() {
EntityPropertyFlags desiredProperties;
desiredProperties += PROP_LOCAL_POSITION;
desiredProperties += PROP_DIMENSIONS;
auto properties = DependencyManager::get<EntityScriptingInterface>()->getEntityProperties(_keyID, desiredProperties);
_originalLocalPosition = properties.getLocalPosition();
_originalDimensions = properties.getDimensions();
_currentLocalPosition = _originalLocalPosition;
_originalDimensionsAndLocalPositionSaved = true;
}
void Key::scaleKey(float sensorToWorldScale) {
if (_originalDimensionsAndLocalPositionSaved) {
glm::vec3 scaledLocalPosition = _originalLocalPosition * sensorToWorldScale;
glm::vec3 scaledDimensions = _originalDimensions * sensorToWorldScale;
_currentLocalPosition = scaledLocalPosition;
EntityItemProperties properties;
properties.setDimensions(scaledDimensions);
properties.setLocalPosition(scaledLocalPosition);
DependencyManager::get<EntityScriptingInterface>()->editEntity(_keyID, properties);
}
}
void Key::startTimer(int time) {
if (_timer) {
_timer->start(time);
_timer->setSingleShot(true);
}
}
bool Key::timerFinished() {
if (_timer) {
return (_timer->remainingTime() <= 0);
}
return false;
}
QString Key::getKeyString(bool toUpper) const {
return toUpper ? _keyString.toUpper() : _keyString;
}
int Key::getScanCode(bool toUpper) const {
QString character = toUpper ? _keyString.toUpper() : _keyString;
auto utf8Key = character.toUtf8();
return (int)utf8Key[0];
}
Key::Type Key::getKeyTypeFromString(const QString& keyTypeString) {
if (keyTypeString == SPACE_STRING) {
return Type::SPACE;
} else if (keyTypeString == BACKSPACE_STRING) {
return Type::BACKSPACE;
} else if (keyTypeString == LAYER_STRING) {
return Type::LAYER;
} else if (keyTypeString == CAPS_STRING) {
return Type::CAPS;
} else if (keyTypeString == CLOSE_STRING) {
return Type::CLOSE;
} else if (keyTypeString == ENTER_STRING) {
return Type::ENTER;
}
return Type::CHARACTER;
}
Keyboard::Keyboard() {
auto pointerManager = DependencyManager::get<PointerManager>();
auto windowScriptingInterface = DependencyManager::get<WindowScriptingInterface>();
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
auto hmdScriptingInterface = DependencyManager::get<HMDScriptingInterface>();
connect(pointerManager.data(), &PointerManager::triggerBeginOverlay, this, &Keyboard::handleTriggerBegin, Qt::QueuedConnection);
connect(pointerManager.data(), &PointerManager::triggerContinueOverlay, this, &Keyboard::handleTriggerContinue, Qt::QueuedConnection);
connect(pointerManager.data(), &PointerManager::triggerEndOverlay, this, &Keyboard::handleTriggerEnd, Qt::QueuedConnection);
connect(pointerManager.data(), &PointerManager::hoverBeginOverlay, this, &Keyboard::handleHoverBegin, Qt::QueuedConnection);
connect(pointerManager.data(), &PointerManager::hoverEndOverlay, this, &Keyboard::handleHoverEnd, Qt::QueuedConnection);
connect(myAvatar.get(), &MyAvatar::sensorToWorldScaleChanged, this, &Keyboard::scaleKeyboard, Qt::QueuedConnection);
connect(windowScriptingInterface.data(), &WindowScriptingInterface::domainChanged, [&]() { setRaised(false); });
connect(hmdScriptingInterface.data(), &HMDScriptingInterface::displayModeChanged, [&]() { setRaised(false); });
}
void Keyboard::registerKeyboardHighlighting() {
auto selection = DependencyManager::get<SelectionScriptingInterface>();
selection->enableListHighlight(KEY_HOVER_HIGHLIGHT, KEY_HOVERING_STYLE);
selection->enableListToScene(KEY_HOVER_HIGHLIGHT);
selection->enableListHighlight(KEY_PRESSED_HIGHLIGHT, KEY_PRESSING_STYLE);
selection->enableListToScene(KEY_PRESSED_HIGHLIGHT);
}
void Keyboard::disableSelectionLists() {
auto selection = DependencyManager::get<SelectionScriptingInterface>();
selection->disableListHighlight(KEY_HOVER_HIGHLIGHT);
selection->disableListHighlight(KEY_PRESSED_HIGHLIGHT);
}
void Keyboard::enableSelectionLists() {
auto selection = DependencyManager::get<SelectionScriptingInterface>();
selection->enableListHighlight(KEY_HOVER_HIGHLIGHT, KEY_HOVERING_STYLE);
selection->enableListHighlight(KEY_PRESSED_HIGHLIGHT, KEY_PRESSING_STYLE);
}
bool Keyboard::getUse3DKeyboard() const {
return _use3DKeyboardLock.resultWithReadLock<bool>([&] {
return _use3DKeyboard.get();
});
}
void Keyboard::setUse3DKeyboard(bool use) {
_use3DKeyboardLock.withWriteLock([&] {
_use3DKeyboard.set(use);
});
}
void Keyboard::createKeyboard() {
auto pointerManager = DependencyManager::get<PointerManager>();
QVariantMap modelProperties {
{ "url", MALLET_MODEL_URL }
};
QVariantMap leftStylusProperties {
{ "hand", LEFT_HAND_CONTROLLER_INDEX },
{ "filter", PickScriptingInterface::PICK_LOCAL_ENTITIES() },
{ "model", modelProperties },
{ "tipOffset", vec3toVariant(MALLET_TIP_OFFSET) }
};
QVariantMap rightStylusProperties {
{ "hand", RIGHT_HAND_CONTROLLER_INDEX },
{ "filter", PickScriptingInterface::PICK_LOCAL_ENTITIES() },
{ "model", modelProperties },
{ "tipOffset", vec3toVariant(MALLET_TIP_OFFSET) }
};
_leftHandStylus = pointerManager->addPointer(std::make_shared<StylusPointer>(leftStylusProperties, StylusPointer::buildStylus(leftStylusProperties), true, true,
MALLET_POSITION_OFFSET, MALLET_ROTATION_OFFSET, MALLET_MODEL_DIMENSIONS));
_rightHandStylus = pointerManager->addPointer(std::make_shared<StylusPointer>(rightStylusProperties, StylusPointer::buildStylus(rightStylusProperties), true, true,
MALLET_POSITION_OFFSET, MALLET_ROTATION_OFFSET, MALLET_MODEL_DIMENSIONS));
pointerManager->disablePointer(_rightHandStylus);
pointerManager->disablePointer(_leftHandStylus);
QString keyboardSvg = PathUtils::resourcesUrl() + "config/keyboard.json";
loadKeyboardFile(keyboardSvg);
_keySound = DependencyManager::get<SoundCache>()->getSound(SOUND_FILE);
}
bool Keyboard::isRaised() const {
return resultWithReadLock<bool>([&] { return _raised; });
}
void Keyboard::setRaised(bool raised) {
bool isRaised;
withReadLock([&] { isRaised = _raised; });
if (isRaised != raised) {
raiseKeyboardAnchor(raised);
raiseKeyboard(raised);
raised ? enableStylus() : disableStylus();
raised ? enableSelectionLists() : disableSelectionLists();
withWriteLock([&] {
_raised = raised;
_layerIndex = 0;
_capsEnabled = false;
_typedCharacters.clear();
});
updateTextDisplay();
}
}
void Keyboard::updateTextDisplay() {
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
float sensorToWorldScale = myAvatar->getSensorToWorldScale();
float textWidth = (float)entityScriptingInterface->textSize(_textDisplay.entityID, _typedCharacters).width();
glm::vec3 scaledDimensions = _textDisplay.dimensions;
scaledDimensions *= sensorToWorldScale;
float leftMargin = (scaledDimensions.x / 2);
scaledDimensions.x += textWidth;
EntityItemProperties properties;
properties.setDimensions(scaledDimensions);
properties.setLeftMargin(leftMargin);
properties.setText(_typedCharacters);
properties.setLineHeight(_textDisplay.lineHeight * sensorToWorldScale);
entityScriptingInterface->editEntity(_textDisplay.entityID, properties);
}
void Keyboard::raiseKeyboardAnchor(bool raise) const {
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
EntityItemProperties properties;
properties.setVisible(raise);
entityScriptingInterface->editEntity(_textDisplay.entityID, properties);
entityScriptingInterface->editEntity(_backPlate.entityID, properties);
if (_resetKeyboardPositionOnRaise) {
std::pair<glm::vec3, glm::quat> keyboardLocation = calculateKeyboardPositionAndOrientation();
properties.setPosition(keyboardLocation.first);
properties.setRotation(keyboardLocation.second);
}
entityScriptingInterface->editEntity(_anchor.entityID, properties);
}
void Keyboard::scaleKeyboard(float sensorToWorldScale) {
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
{
EntityItemProperties properties;
properties.setLocalPosition(_backPlate.localPosition * sensorToWorldScale);
properties.setDimensions(_backPlate.dimensions * sensorToWorldScale);
entityScriptingInterface->editEntity(_backPlate.entityID, properties);
}
for (auto& keyboardLayer : _keyboardLayers) {
for (auto iter = keyboardLayer.begin(); iter != keyboardLayer.end(); iter++) {
iter.value().scaleKey(sensorToWorldScale);
}
}
{
EntityItemProperties properties;
properties.setLocalPosition(_textDisplay.localPosition * sensorToWorldScale);
properties.setDimensions(_textDisplay.dimensions * sensorToWorldScale);
properties.setLineHeight(_textDisplay.lineHeight * sensorToWorldScale);
entityScriptingInterface->editEntity(_textDisplay.entityID, properties);
}
}
void Keyboard::startLayerSwitchTimer() {
if (_layerSwitchTimer) {
_layerSwitchTimer->start(LAYER_SWITCH_TIMEOUT_MS);
_layerSwitchTimer->setSingleShot(true);
}
}
bool Keyboard::isLayerSwitchTimerFinished() const {
if (_layerSwitchTimer) {
return (_layerSwitchTimer->remainingTime() <= 0);
}
return false;
}
void Keyboard::raiseKeyboard(bool raise) const {
if (_keyboardLayers.empty()) {
return;
}
const auto& keyboardLayer = _keyboardLayers[_layerIndex];
EntityItemProperties properties;
properties.setVisible(raise);
for (auto iter = keyboardLayer.begin(); iter != keyboardLayer.end(); iter++) {
DependencyManager::get<EntityScriptingInterface>()->editEntity(iter.key(), properties);
}
}
bool Keyboard::isPassword() const {
return resultWithReadLock<bool>([&] { return _password; });
}
void Keyboard::setPassword(bool password) {
if (_password != password) {
withWriteLock([&] {
_password = password;
_typedCharacters.clear();
});
}
updateTextDisplay();
}
void Keyboard::setResetKeyboardPositionOnRaise(bool reset) {
if (_resetKeyboardPositionOnRaise != reset) {
withWriteLock([&] {
_resetKeyboardPositionOnRaise = reset;
});
}
}
void Keyboard::setPreferMalletsOverLasers(bool preferMalletsOverLasers) {
_preferMalletsOverLasersSettingLock.withWriteLock([&] {
_preferMalletsOverLasers.set(preferMalletsOverLasers);
});
}
bool Keyboard::getPreferMalletsOverLasers() const {
return _preferMalletsOverLasersSettingLock.resultWithReadLock<bool>([&] {
return _preferMalletsOverLasers.get();
});
}
void Keyboard::switchToLayer(int layerIndex) {
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
if (layerIndex >= 0 && layerIndex < (int)_keyboardLayers.size()) {
EntityPropertyFlags desiredProperties;
desiredProperties += PROP_POSITION;
desiredProperties += PROP_ROTATION;
auto oldProperties = entityScriptingInterface->getEntityProperties(_anchor.entityID, desiredProperties);
glm::vec3 currentPosition = oldProperties.getPosition();
glm::quat currentOrientation = oldProperties.getRotation();
raiseKeyboardAnchor(false);
raiseKeyboard(false);
setLayerIndex(layerIndex);
raiseKeyboardAnchor(true);
raiseKeyboard(true);
EntityItemProperties properties;
properties.setPosition(currentPosition);
properties.setRotation(currentOrientation);
entityScriptingInterface->editEntity(_anchor.entityID, properties);
startLayerSwitchTimer();
}
}
bool Keyboard::shouldProcessEntityAndPointerEvent(const PointerEvent& event) const {
return (shouldProcessPointerEvent(event) && shouldProcessEntity());
}
bool Keyboard::shouldProcessPointerEvent(const PointerEvent& event) const {
bool preferMalletsOverLasers = getPreferMalletsOverLasers();
unsigned int pointerID = event.getID();
bool isStylusEvent = (pointerID == _leftHandStylus || pointerID == _rightHandStylus);
bool isLaserEvent = (pointerID == _leftHandLaser || pointerID == _rightHandLaser);
return ((isStylusEvent && preferMalletsOverLasers) || (isLaserEvent && !preferMalletsOverLasers));
}
void Keyboard::handleTriggerBegin(const QUuid& id, const PointerEvent& event) {
auto buttonType = event.getButton();
if (!shouldProcessEntityAndPointerEvent(event) || buttonType != PointerEvent::PrimaryButton) {
return;
}
auto& keyboardLayer = _keyboardLayers[_layerIndex];
auto search = keyboardLayer.find(id);
if (search == keyboardLayer.end()) {
return;
}
Key& key = search.value();
if (key.timerFinished()) {
unsigned int pointerID = event.getID();
auto handIndex = (pointerID == _leftHandStylus || pointerID == _leftHandLaser)
? controller::Hand::LEFT : controller::Hand::RIGHT;
auto userInputMapper = DependencyManager::get<UserInputMapper>();
userInputMapper->triggerHapticPulse(PULSE_STRENGTH, PULSE_DURATION, handIndex);
EntityPropertyFlags desiredProperties;
desiredProperties += PROP_POSITION;
glm::vec3 keyWorldPosition = DependencyManager::get<EntityScriptingInterface>()->getEntityProperties(id, desiredProperties).getPosition();
AudioInjectorOptions audioOptions;
audioOptions.localOnly = true;
audioOptions.position = keyWorldPosition;
audioOptions.volume = 0.05f;
AudioInjector::playSoundAndDelete(_keySound, audioOptions);
int scanCode = key.getScanCode(_capsEnabled);
QString keyString = key.getKeyString(_capsEnabled);
auto tablet = DependencyManager::get<TabletScriptingInterface>()->getTablet("com.highfidelity.interface.tablet.system");
switch (key.getKeyType()) {
case Key::Type::CLOSE:
setRaised(false);
tablet->unfocus();
return;
case Key::Type::CAPS:
_capsEnabled = !_capsEnabled;
switchToLayer(key.getSwitchToLayerIndex());
return;
case Key::Type::LAYER:
_capsEnabled = false;
switchToLayer(key.getSwitchToLayerIndex());
return;
case Key::Type::BACKSPACE:
scanCode = Qt::Key_Backspace;
keyString = "\x08";
_typedCharacters = _typedCharacters.left(_typedCharacters.length() -1);
updateTextDisplay();
break;
case Key::Type::ENTER:
scanCode = Qt::Key_Return;
keyString = "\x0d";
_typedCharacters.clear();
updateTextDisplay();
break;
case Key::Type::CHARACTER:
if (keyString != " ") {
_typedCharacters.push_back((_password ? "*" : keyString));
} else {
_typedCharacters.clear();
}
updateTextDisplay();
break;
default:
break;
}
QKeyEvent* pressEvent = new QKeyEvent(QEvent::KeyPress, scanCode, Qt::NoModifier, keyString);
QKeyEvent* releaseEvent = new QKeyEvent(QEvent::KeyRelease, scanCode, Qt::NoModifier, keyString);
QCoreApplication::postEvent(QCoreApplication::instance(), pressEvent);
QCoreApplication::postEvent(QCoreApplication::instance(), releaseEvent);
if (!getPreferMalletsOverLasers()) {
key.startTimer(KEY_PRESS_TIMEOUT_MS);
}
auto selection = DependencyManager::get<SelectionScriptingInterface>();
selection->addToSelectedItemsList(KEY_PRESSED_HIGHLIGHT, "entity", id);
}
}
void Keyboard::setLeftHandLaser(unsigned int leftHandLaser) {
_handLaserLock.withWriteLock([&] {
_leftHandLaser = leftHandLaser;
});
}
void Keyboard::setRightHandLaser(unsigned int rightHandLaser) {
_handLaserLock.withWriteLock([&] {
_rightHandLaser = rightHandLaser;
});
}
void Keyboard::handleTriggerEnd(const QUuid& id, const PointerEvent& event) {
if (!shouldProcessEntityAndPointerEvent(event)) {
return;
}
auto& keyboardLayer = _keyboardLayers[_layerIndex];
auto search = keyboardLayer.find(id);
if (search == keyboardLayer.end()) {
return;
}
Key& key = search.value();
EntityItemProperties properties;
properties.setLocalPosition(key.getCurrentLocalPosition());
DependencyManager::get<EntityScriptingInterface>()->editEntity(id, properties);
key.setIsPressed(false);
if (key.timerFinished() && getPreferMalletsOverLasers()) {
key.startTimer(KEY_PRESS_TIMEOUT_MS);
}
auto selection = DependencyManager::get<SelectionScriptingInterface>();
selection->removeFromSelectedItemsList(KEY_PRESSED_HIGHLIGHT, "entity", id);
}
void Keyboard::handleTriggerContinue(const QUuid& id, const PointerEvent& event) {
if (!shouldProcessEntityAndPointerEvent(event)) {
return;
}
auto& keyboardLayer = _keyboardLayers[_layerIndex];
auto search = keyboardLayer.find(id);
if (search == keyboardLayer.end()) {
return;
}
Key& key = search.value();
if (!key.isPressed() && getPreferMalletsOverLasers()) {
unsigned int pointerID = event.getID();
auto pointerManager = DependencyManager::get<PointerManager>();
auto pickResult = pointerManager->getPrevPickResult(pointerID);
auto stylusPickResult = std::dynamic_pointer_cast<StylusPickResult>(pickResult);
float distance = stylusPickResult->distance;
static const float PENATRATION_THRESHOLD = 0.025f;
if (distance < PENATRATION_THRESHOLD) {
static const float Z_OFFSET = 0.002f;
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
EntityPropertyFlags desiredProperties;
desiredProperties += PROP_ROTATION;
glm::quat orientation = entityScriptingInterface->getEntityProperties(id, desiredProperties).getRotation();
glm::vec3 yAxis = orientation * Z_AXIS;
glm::vec3 yOffset = yAxis * Z_OFFSET;
glm::vec3 localPosition = key.getCurrentLocalPosition() - yOffset;
EntityItemProperties properties;
properties.setLocalPosition(localPosition);
entityScriptingInterface->editEntity(id, properties);
key.setIsPressed(true);
}
}
}
void Keyboard::handleHoverBegin(const QUuid& id, const PointerEvent& event) {
if (!shouldProcessEntityAndPointerEvent(event)) {
return;
}
auto& keyboardLayer = _keyboardLayers[_layerIndex];
auto search = keyboardLayer.find(id);
if (search == keyboardLayer.end()) {
return;
}
auto selection = DependencyManager::get<SelectionScriptingInterface>();
selection->addToSelectedItemsList(KEY_HOVER_HIGHLIGHT, "entity", id);
}
void Keyboard::handleHoverEnd(const QUuid& id, const PointerEvent& event) {
if (!shouldProcessEntityAndPointerEvent(event)) {
return;
}
auto& keyboardLayer = _keyboardLayers[_layerIndex];
auto search = keyboardLayer.find(id);
if (search == keyboardLayer.end()) {
return;
}
auto selection = DependencyManager::get<SelectionScriptingInterface>();
selection->removeFromSelectedItemsList(KEY_HOVER_HIGHLIGHT, "entity", id);
}
void Keyboard::disableStylus() {
auto pointerManager = DependencyManager::get<PointerManager>();
pointerManager->setRenderState(_leftHandStylus, "events off");
pointerManager->disablePointer(_leftHandStylus);
pointerManager->setRenderState(_rightHandStylus, "events off");
pointerManager->disablePointer(_rightHandStylus);
}
void Keyboard::setLayerIndex(int layerIndex) {
if (layerIndex >= 0 && layerIndex < (int)_keyboardLayers.size()) {
_layerIndex = layerIndex;
} else {
_layerIndex = 0;
}
}
void Keyboard::loadKeyboardFile(const QString& keyboardFile) {
if (keyboardFile.isEmpty()) {
return;
}
auto request = DependencyManager::get<ResourceManager>()->createResourceRequest(this, keyboardFile);
if (!request) {
qCWarning(interfaceapp) << "Could not create resource for Keyboard file" << keyboardFile;
}
connect(request, &ResourceRequest::finished, this, [=]() {
if (request->getResult() != ResourceRequest::Success) {
qCWarning(interfaceapp) << "Keyboard file failed to download";
return;
}
clearKeyboardKeys();
auto requestData = request->getData();
QVector<QUuid> includeItems;
QJsonParseError parseError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(requestData, &parseError);
if (parseError.error != QJsonParseError::NoError) {
qCWarning(interfaceapp) << "Failed to parse keyboard json file - Error: " << parseError.errorString();
return;
}
QJsonObject jsonObject = jsonDoc.object();
QJsonArray layer = jsonObject["Layer1"].toArray();
QJsonObject anchorObject = jsonObject["anchor"].toObject();
bool useResourcePath = jsonObject["useResourcesPath"].toBool();
QString resourcePath = PathUtils::resourcesUrl();
if (anchorObject.isEmpty()) {
qCWarning(interfaceapp) << "No Anchor specified. Not creating keyboard";
return;
}
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
{
glm::vec3 dimensions = vec3FromVariant(anchorObject["dimensions"].toVariant());
EntityItemProperties properties;
properties.setType(EntityTypes::Box);
properties.setName("KeyboardAnchor");
properties.setVisible(false);
properties.getGrab().setGrabbable(true);
properties.setIgnorePickIntersection(false);
properties.setDimensions(dimensions);
properties.setPosition(vec3FromVariant(anchorObject["position"].toVariant()));
properties.setRotation(quatFromVariant(anchorObject["rotation"].toVariant()));
Anchor anchor;
anchor.entityID = entityScriptingInterface->addEntityInternal(properties, entity::HostType::LOCAL);
anchor.originalDimensions = dimensions;
_anchor = anchor;
}
{
QJsonObject backPlateObject = jsonObject["backPlate"].toObject();
glm::vec3 position = vec3FromVariant(backPlateObject["position"].toVariant());
glm::vec3 dimensions = vec3FromVariant(backPlateObject["dimensions"].toVariant());
glm::quat rotation = quatFromVariant(backPlateObject["rotation"].toVariant());
EntityItemProperties properties;
properties.setType(EntityTypes::Box);
properties.setName("Keyboard-BackPlate");
properties.setVisible(true);
properties.getGrab().setGrabbable(false);
properties.setAlpha(0.0f);
properties.setIgnorePickIntersection(false);
properties.setDimensions(dimensions);
properties.setPosition(position);
properties.setRotation(rotation);
properties.setParentID(_anchor.entityID);
BackPlate backPlate;
backPlate.entityID = entityScriptingInterface->addEntityInternal(properties, entity::HostType::LOCAL);
backPlate.dimensions = dimensions;
glm::quat anchorEntityInverseWorldOrientation = glm::inverse(rotation);
glm::vec3 anchorEntityLocalTranslation = anchorEntityInverseWorldOrientation * -position;
backPlate.localPosition = (anchorEntityInverseWorldOrientation * position) + anchorEntityLocalTranslation;
_backPlate = backPlate;
}
const QJsonArray& keyboardLayers = jsonObject["layers"].toArray();
int keyboardLayerCount = keyboardLayers.size();
_keyboardLayers.reserve(keyboardLayerCount);
for (int keyboardLayerIndex = 0; keyboardLayerIndex < keyboardLayerCount; keyboardLayerIndex++) {
const QJsonValue& keyboardLayer = keyboardLayers[keyboardLayerIndex].toArray();
QHash<QUuid, Key> keyboardLayerKeys;
foreach (const QJsonValue& keyboardKeyValue, keyboardLayer.toArray()) {
QVariantMap textureMap;
if (!keyboardKeyValue["texture"].isNull()) {
textureMap = keyboardKeyValue["texture"].toObject().toVariantMap();
if (useResourcePath) {
for (auto iter = textureMap.begin(); iter != textureMap.end(); iter++) {
QString modifiedPath = resourcePath + iter.value().toString();
textureMap[iter.key()] = modifiedPath;
}
}
}
QString modelUrl = keyboardKeyValue["modelURL"].toString();
QString url = (useResourcePath ? (resourcePath + modelUrl) : modelUrl);
EntityItemProperties properties;
properties.setType(EntityTypes::Model);
properties.setDimensions(vec3FromVariant(keyboardKeyValue["dimensions"].toVariant()));
properties.setPosition(vec3FromVariant(keyboardKeyValue["position"].toVariant()));
properties.setVisible(false);
properties.setEmissive(true);
properties.setParentID(_anchor.entityID);
properties.setModelURL(url);
properties.setTextures(QVariant(textureMap).toString());
properties.getGrab().setGrabbable(false);
properties.setLocalRotation(quatFromVariant(keyboardKeyValue["localOrientation"].toVariant()));
QUuid id = entityScriptingInterface->addEntityInternal(properties, entity::HostType::LOCAL);
QString keyType = keyboardKeyValue["type"].toString();
QString keyString = keyboardKeyValue["key"].toString();
Key key;
if (!keyType.isNull()) {
Key::Type type= Key::getKeyTypeFromString(keyType);
key.setKeyType(type);
if (type == Key::Type::LAYER || type == Key::Type::CAPS) {
int switchToLayer = keyboardKeyValue["switchToLayer"].toInt();
key.setSwitchToLayerIndex(switchToLayer);
}
}
key.setID(id);
key.setKeyString(keyString);
key.saveDimensionsAndLocalPosition();
includeItems.append(key.getID());
_itemsToIgnore.append(key.getID());
keyboardLayerKeys.insert(id, key);
}
_keyboardLayers.push_back(keyboardLayerKeys);
}
{
QJsonObject displayTextObject = jsonObject["textDisplay"].toObject();
glm::vec3 dimensions = vec3FromVariant(displayTextObject["dimensions"].toVariant());
glm::vec3 localPosition = vec3FromVariant(displayTextObject["localPosition"].toVariant());
float lineHeight = (float)displayTextObject["lineHeight"].toDouble();
EntityItemProperties properties;
properties.setType(EntityTypes::Text);
properties.setDimensions(dimensions);
properties.setLocalPosition(localPosition);
properties.setLocalRotation(quatFromVariant(displayTextObject["localOrientation"].toVariant()));
properties.setLeftMargin((float)displayTextObject["leftMargin"].toDouble());
properties.setRightMargin((float)displayTextObject["rightMargin"].toDouble());
properties.setTopMargin((float)displayTextObject["topMargin"].toDouble());
properties.setBottomMargin((float)displayTextObject["bottomMargin"].toDouble());
properties.setLineHeight((float)displayTextObject["lineHeight"].toDouble());
properties.setVisible(false);
properties.setEmissive(true);
properties.getGrab().setGrabbable(false);
properties.setText("");
properties.setParentID(_anchor.entityID);
TextDisplay textDisplay;
textDisplay.entityID = entityScriptingInterface->addEntityInternal(properties, entity::HostType::LOCAL);
textDisplay.localPosition = localPosition;
textDisplay.dimensions = dimensions;
textDisplay.lineHeight = lineHeight;
_textDisplay = textDisplay;
}
_ignoreItemsLock.withWriteLock([&] {
_itemsToIgnore.append(_textDisplay.entityID);
_itemsToIgnore.append(_anchor.entityID);
});
_layerIndex = 0;
auto pointerManager = DependencyManager::get<PointerManager>();
pointerManager->setIncludeItems(_leftHandStylus, includeItems);
pointerManager->setIncludeItems(_rightHandStylus, includeItems);
});
request->send();
}
QUuid Keyboard::getAnchorID() {
return _ignoreItemsLock.resultWithReadLock<QUuid>([&] {
return _anchor.entityID;
});
}
bool Keyboard::shouldProcessEntity() const {
return (!_keyboardLayers.empty() && isLayerSwitchTimerFinished());
}
QVector<QUuid> Keyboard::getKeysID() {
return _ignoreItemsLock.resultWithReadLock<QVector<QUuid>>([&] {
return _itemsToIgnore;
});
}
void Keyboard::clearKeyboardKeys() {
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
for (const auto& keyboardLayer: _keyboardLayers) {
for (auto iter = keyboardLayer.begin(); iter != keyboardLayer.end(); iter++) {
entityScriptingInterface->deleteEntity(iter.key());
}
}
entityScriptingInterface->deleteEntity(_anchor.entityID);
entityScriptingInterface->deleteEntity(_textDisplay.entityID);
entityScriptingInterface->deleteEntity(_backPlate.entityID);
_keyboardLayers.clear();
_ignoreItemsLock.withWriteLock([&] {
_itemsToIgnore.clear();
});
}
void Keyboard::enableStylus() {
if (getPreferMalletsOverLasers()) {
enableRightMallet();
enableLeftMallet();
}
}
void Keyboard::enableRightMallet() {
auto pointerManager = DependencyManager::get<PointerManager>();
pointerManager->setRenderState(_rightHandStylus, "events on");
pointerManager->enablePointer(_rightHandStylus);
}
void Keyboard::enableLeftMallet() {
auto pointerManager = DependencyManager::get<PointerManager>();
pointerManager->setRenderState(_leftHandStylus, "events on");
pointerManager->enablePointer(_leftHandStylus);
}
void Keyboard::disableLeftMallet() {
auto pointerManager = DependencyManager::get<PointerManager>();
pointerManager->setRenderState(_leftHandStylus, "events off");
pointerManager->disablePointer(_leftHandStylus);
}
void Keyboard::disableRightMallet() {
auto pointerManager = DependencyManager::get<PointerManager>();
pointerManager->setRenderState(_rightHandStylus, "events off");
pointerManager->disablePointer(_rightHandStylus);
}
bool Keyboard::containsID(const QUuid& id) const {
return resultWithReadLock<bool>([&] {
return _itemsToIgnore.contains(id) || _backPlate.entityID == id;
});
}