overte/interface/src/ui/Keyboard.cpp
2018-11-30 13:05:17 -08:00

912 lines
33 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 "ui/overlays/Overlays.h"
#include "ui/overlays/Overlay.h"
#include "ui/overlays/ModelOverlay.h"
#include "ui/overlays/Cube3DOverlay.h"
#include "ui/overlays/Text3DOverlay.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()) {
Overlays& overlays = qApp->getOverlays();
auto tabletOverlay = std::dynamic_pointer_cast<Base3DOverlay>(overlays.getOverlay(tabletID));
if (tabletOverlay) {
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 = tabletOverlay->getWorldPosition();
glm::quat tabletWorldOrientation = tabletOverlay->getWorldOrientation();
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() {
Overlays& overlays = qApp->getOverlays();
auto model3DOverlay = std::dynamic_pointer_cast<ModelOverlay>(overlays.getOverlay(_keyID));
if (model3DOverlay) {
_originalLocalPosition = model3DOverlay->getLocalPosition();
_originalDimensions = model3DOverlay->getDimensions();
_currentLocalPosition = _originalLocalPosition;
}
}
void Key::scaleKey(float sensorToWorldScale) {
Overlays& overlays = qApp->getOverlays();
auto model3DOverlay = std::dynamic_pointer_cast<ModelOverlay>(overlays.getOverlay(_keyID));
if (model3DOverlay) {
glm::vec3 scaledLocalPosition = _originalLocalPosition * sensorToWorldScale;
glm::vec3 scaledDimensions = _originalDimensions * sensorToWorldScale;
_currentLocalPosition = scaledLocalPosition;
QVariantMap properties {
{ "dimensions", vec3toVariant(scaledDimensions) },
{ "localPosition", vec3toVariant(scaledLocalPosition) }
};
overlays.editOverlay(_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);
}
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_OVERLAYS() },
{ "model", modelProperties },
{ "tipOffset", vec3toVariant(MALLET_TIP_OFFSET) }
};
QVariantMap rightStylusProperties {
{ "hand", RIGHT_HAND_CONTROLLER_INDEX },
{ "filter", PickScriptingInterface::PICK_OVERLAYS() },
{ "model", modelProperties },
{ "tipOffset", vec3toVariant(MALLET_TIP_OFFSET) }
};
_leftHandStylus = pointerManager->addPointer(std::make_shared<StylusPointer>(leftStylusProperties, StylusPointer::buildStylusOverlay(leftStylusProperties), true, true,
MALLET_POSITION_OFFSET, MALLET_ROTATION_OFFSET, MALLET_MODEL_DIMENSIONS));
_rightHandStylus = pointerManager->addPointer(std::make_shared<StylusPointer>(rightStylusProperties, StylusPointer::buildStylusOverlay(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();
withWriteLock([&] {
_raised = raised;
_layerIndex = 0;
_capsEnabled = false;
_typedCharacters.clear();
});
updateTextDisplay();
}
}
void Keyboard::updateTextDisplay() {
Overlays& overlays = qApp->getOverlays();
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
float sensorToWorldScale = myAvatar->getSensorToWorldScale();
float textWidth = (float) overlays.textSize(_textDisplay.overlayID, _typedCharacters).width();
glm::vec3 scaledDimensions = _textDisplay.dimensions;
scaledDimensions *= sensorToWorldScale;
float leftMargin = (scaledDimensions.x / 2);
scaledDimensions.x += textWidth;
QVariantMap textDisplayProperties {
{ "dimensions", vec3toVariant(scaledDimensions) },
{ "leftMargin", leftMargin },
{ "text", _typedCharacters },
{ "lineHeight", (_textDisplay.lineHeight * sensorToWorldScale) }
};
overlays.editOverlay(_textDisplay.overlayID, textDisplayProperties);
}
void Keyboard::raiseKeyboardAnchor(bool raise) const {
Overlays& overlays = qApp->getOverlays();
OverlayID anchorOverlayID = _anchor.overlayID;
auto anchorOverlay = std::dynamic_pointer_cast<Cube3DOverlay>(overlays.getOverlay(anchorOverlayID));
if (anchorOverlay) {
std::pair<glm::vec3, glm::quat> keyboardLocation = calculateKeyboardPositionAndOrientation();
if (_resetKeyboardPositionOnRaise) {
anchorOverlay->setWorldPosition(keyboardLocation.first);
anchorOverlay->setWorldOrientation(keyboardLocation.second);
}
anchorOverlay->setVisible(raise);
QVariantMap textDisplayProperties {
{ "visible", raise }
};
overlays.editOverlay(_textDisplay.overlayID, textDisplayProperties);
}
}
void Keyboard::scaleKeyboard(float sensorToWorldScale) {
Overlays& overlays = qApp->getOverlays();
glm::vec3 scaledDimensions = _anchor.originalDimensions * sensorToWorldScale;
auto volume3DOverlay = std::dynamic_pointer_cast<Volume3DOverlay>(overlays.getOverlay(_anchor.overlayID));
if (volume3DOverlay) {
volume3DOverlay->setDimensions(scaledDimensions);
}
for (auto& keyboardLayer: _keyboardLayers) {
for (auto iter = keyboardLayer.begin(); iter != keyboardLayer.end(); iter++) {
iter.value().scaleKey(sensorToWorldScale);
}
}
glm::vec3 scaledLocalPosition = _textDisplay.localPosition * sensorToWorldScale;
glm::vec3 textDisplayScaledDimensions = _textDisplay.dimensions * sensorToWorldScale;
QVariantMap textDisplayProperties {
{ "localPosition", vec3toVariant(scaledLocalPosition) },
{ "dimensions", vec3toVariant(textDisplayScaledDimensions) },
{ "lineHeight", (_textDisplay.lineHeight * sensorToWorldScale) }
};
overlays.editOverlay(_textDisplay.overlayID, textDisplayProperties);
}
void Keyboard::startLayerSwitchTimer() {
if (_layerSwitchTimer) {
_layerSwitchTimer->start(LAYER_SWITCH_TIMEOUT_MS);
_layerSwitchTimer->setSingleShot(true);
}
}
bool Keyboard::isLayerSwitchTimerFinished() {
if (_layerSwitchTimer) {
return (_layerSwitchTimer->remainingTime() <= 0);
}
return false;
}
void Keyboard::raiseKeyboard(bool raise) const {
if (_keyboardLayers.empty()) {
return;
}
Overlays& overlays = qApp->getOverlays();
const auto& keyboardLayer = _keyboardLayers[_layerIndex];
for (auto iter = keyboardLayer.begin(); iter != keyboardLayer.end(); iter++) {
auto base3DOverlay = std::dynamic_pointer_cast<Base3DOverlay>(overlays.getOverlay(iter.key()));
if (base3DOverlay) {
base3DOverlay->setVisible(raise);
}
}
}
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::switchToLayer(int layerIndex) {
if (layerIndex >= 0 && layerIndex < (int)_keyboardLayers.size()) {
Overlays& overlays = qApp->getOverlays();
OverlayID currentAnchorOverlayID = _anchor.overlayID;
glm::vec3 currentOverlayPosition;
glm::quat currentOverlayOrientation;
auto currentAnchorOverlay = std::dynamic_pointer_cast<Cube3DOverlay>(overlays.getOverlay(currentAnchorOverlayID));
if (currentAnchorOverlay) {
currentOverlayPosition = currentAnchorOverlay->getWorldPosition();
currentOverlayOrientation = currentAnchorOverlay->getWorldOrientation();
}
raiseKeyboardAnchor(false);
raiseKeyboard(false);
setLayerIndex(layerIndex);
raiseKeyboardAnchor(true);
raiseKeyboard(true);
OverlayID newAnchorOverlayID = _anchor.overlayID;
auto newAnchorOverlay = std::dynamic_pointer_cast<Cube3DOverlay>(overlays.getOverlay(newAnchorOverlayID));
if (newAnchorOverlay) {
newAnchorOverlay->setWorldPosition(currentOverlayPosition);
newAnchorOverlay->setWorldOrientation(currentOverlayOrientation);
}
startLayerSwitchTimer();
}
}
void Keyboard::handleTriggerBegin(const OverlayID& overlayID, const PointerEvent& event) {
if (_keyboardLayers.empty() || !isLayerSwitchTimerFinished()) {
return;
}
auto pointerID = event.getID();
auto buttonType = event.getButton();
if ((pointerID != _leftHandStylus && pointerID != _rightHandStylus) || buttonType != PointerEvent::PrimaryButton) {
return;
}
auto& keyboardLayer = _keyboardLayers[_layerIndex];
auto search = keyboardLayer.find(overlayID);
if (search == keyboardLayer.end()) {
return;
}
Key& key = search.value();
if (key.timerFinished()) {
auto handIndex = (pointerID == _leftHandStylus) ? controller::Hand::LEFT : controller::Hand::RIGHT;
auto userInputMapper = DependencyManager::get<UserInputMapper>();
userInputMapper->triggerHapticPulse(PULSE_STRENGTH, PULSE_DURATION, handIndex);
Overlays& overlays = qApp->getOverlays();
auto base3DOverlay = std::dynamic_pointer_cast<Base3DOverlay>(overlays.getOverlay(overlayID));
glm::vec3 keyWorldPosition;
if (base3DOverlay) {
keyWorldPosition = base3DOverlay->getWorldPosition();
}
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);
key.startTimer(KEY_PRESS_TIMEOUT_MS);
auto selection = DependencyManager::get<SelectionScriptingInterface>();
selection->addToSelectedItemsList(KEY_PRESSED_HIGHLIGHT, "overlay", overlayID);
}
}
void Keyboard::handleTriggerEnd(const OverlayID& overlayID, const PointerEvent& event) {
if (_keyboardLayers.empty() || !isLayerSwitchTimerFinished()) {
return;
}
auto pointerID = event.getID();
if (pointerID != _leftHandStylus && pointerID != _rightHandStylus) {
return;
}
auto& keyboardLayer = _keyboardLayers[_layerIndex];
auto search = keyboardLayer.find(overlayID);
if (search == keyboardLayer.end()) {
return;
}
Key& key = search.value();;
Overlays& overlays = qApp->getOverlays();
auto base3DOverlay = std::dynamic_pointer_cast<Base3DOverlay>(overlays.getOverlay(overlayID));
if (base3DOverlay) {
base3DOverlay->setLocalPosition(key.getCurrentLocalPosition());
}
key.setIsPressed(false);
if (key.timerFinished()) {
key.startTimer(KEY_PRESS_TIMEOUT_MS);
}
auto selection = DependencyManager::get<SelectionScriptingInterface>();
selection->removeFromSelectedItemsList(KEY_PRESSED_HIGHLIGHT, "overlay", overlayID);
}
void Keyboard::handleTriggerContinue(const OverlayID& overlayID, const PointerEvent& event) {
if (_keyboardLayers.empty() || !isLayerSwitchTimerFinished()) {
return;
}
auto pointerID = event.getID();
if (pointerID != _leftHandStylus && pointerID != _rightHandStylus) {
return;
}
auto& keyboardLayer = _keyboardLayers[_layerIndex];
auto search = keyboardLayer.find(overlayID);
if (search == keyboardLayer.end()) {
return;
}
Key& key = search.value();
Overlays& overlays = qApp->getOverlays();
if (!key.isPressed()) {
auto base3DOverlay = std::dynamic_pointer_cast<Base3DOverlay>(overlays.getOverlay(overlayID));
if (base3DOverlay) {
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;
glm::quat overlayOrientation = base3DOverlay->getWorldOrientation();
glm::vec3 overlayYAxis = overlayOrientation * Z_AXIS;
glm::vec3 overlayYOffset = overlayYAxis * Z_OFFSET;
glm::vec3 localPosition = key.getCurrentLocalPosition() - overlayYOffset;
base3DOverlay->setLocalPosition(localPosition);
key.setIsPressed(true);
}
}
}
}
void Keyboard::handleHoverBegin(const OverlayID& overlayID, const PointerEvent& event) {
if (_keyboardLayers.empty() || !isLayerSwitchTimerFinished()) {
return;
}
auto pointerID = event.getID();
if (pointerID != _leftHandStylus && pointerID != _rightHandStylus) {
return;
}
auto& keyboardLayer = _keyboardLayers[_layerIndex];
auto search = keyboardLayer.find(overlayID);
if (search == keyboardLayer.end()) {
return;
}
auto selection = DependencyManager::get<SelectionScriptingInterface>();
selection->addToSelectedItemsList(KEY_HOVER_HIGHLIGHT, "overlay", overlayID);
}
void Keyboard::handleHoverEnd(const OverlayID& overlayID, const PointerEvent& event) {
if (_keyboardLayers.empty() || !isLayerSwitchTimerFinished()) {
return;
}
auto pointerID = event.getID();
if (pointerID != _leftHandStylus && pointerID != _rightHandStylus) {
return;
}
auto& keyboardLayer = _keyboardLayers[_layerIndex];
auto search = keyboardLayer.find(overlayID);
if (search == keyboardLayer.end()) {
return;
}
auto selection = DependencyManager::get<SelectionScriptingInterface>();
selection->removeFromSelectedItemsList(KEY_HOVER_HIGHLIGHT, "overlay", overlayID);
}
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();
Overlays& overlays = qApp->getOverlays();
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;
}
QVariantMap anchorProperties {
{ "name", "KeyboardAnchor"},
{ "isSolid", true },
{ "visible", false },
{ "grabbable", true },
{ "ignoreRayIntersection", false },
{ "dimensions", anchorObject["dimensions"].toVariant() },
{ "position", anchorObject["position"].toVariant() },
{ "orientation", anchorObject["rotation"].toVariant() }
};
glm::vec3 dimensions = vec3FromVariant(anchorObject["dimensions"].toVariant());
Anchor anchor;
anchor.overlayID = overlays.addOverlay("cube", anchorProperties);
anchor.originalDimensions = dimensions;
_anchor = anchor;
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<OverlayID, 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);
QVariantMap properties {
{ "dimensions", keyboardKeyValue["dimensions"].toVariant() },
{ "position", keyboardKeyValue["position"].toVariant() },
{ "visible", false },
{ "isSolid", true },
{ "emissive", true },
{ "parentID", _anchor.overlayID },
{ "url", url },
{ "textures", textureMap },
{ "grabbable", false },
{ "localOrientation", keyboardKeyValue["localOrientation"].toVariant() }
};
OverlayID overlayID = overlays.addOverlay("model", properties);
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(overlayID);
key.setKeyString(keyString);
key.saveDimensionsAndLocalPosition();
includeItems.append(key.getID());
_itemsToIgnore.append(key.getID());
keyboardLayerKeys.insert(overlayID, key);
}
_keyboardLayers.push_back(keyboardLayerKeys);
}
TextDisplay textDisplay;
QJsonObject displayTextObject = jsonObject["textDisplay"].toObject();
QVariantMap displayTextProperties {
{ "dimensions", displayTextObject["dimensions"].toVariant() },
{ "localPosition", displayTextObject["localPosition"].toVariant() },
{ "localOrientation", displayTextObject["localOrientation"].toVariant() },
{ "leftMargin", displayTextObject["leftMargin"].toVariant() },
{ "rightMargin", displayTextObject["rightMargin"].toVariant() },
{ "topMargin", displayTextObject["topMargin"].toVariant() },
{ "bottomMargin", displayTextObject["bottomMargin"].toVariant() },
{ "lineHeight", displayTextObject["lineHeight"].toVariant() },
{ "visible", false },
{ "emissive", true },
{ "grabbable", false },
{ "text", ""},
{ "parentID", _anchor.overlayID }
};
textDisplay.overlayID = overlays.addOverlay("text3d", displayTextProperties);
textDisplay.localPosition = vec3FromVariant(displayTextObject["localPosition"].toVariant());
textDisplay.dimensions = vec3FromVariant(displayTextObject["dimensions"].toVariant());
textDisplay.lineHeight = (float) displayTextObject["lineHeight"].toDouble();
_textDisplay = textDisplay;
_ignoreItemsLock.withWriteLock([&] {
_itemsToIgnore.append(_textDisplay.overlayID);
_itemsToIgnore.append(_anchor.overlayID);
});
_layerIndex = 0;
auto pointerManager = DependencyManager::get<PointerManager>();
pointerManager->setIncludeItems(_leftHandStylus, includeItems);
pointerManager->setIncludeItems(_rightHandStylus, includeItems);
});
request->send();
}
OverlayID Keyboard::getAnchorID() {
return _ignoreItemsLock.resultWithReadLock<OverlayID>([&] {
return _anchor.overlayID;
});
}
QVector<OverlayID> Keyboard::getKeysID() {
return _ignoreItemsLock.resultWithReadLock<QVector<OverlayID>>([&] {
return _itemsToIgnore;
});
}
void Keyboard::clearKeyboardKeys() {
Overlays& overlays = qApp->getOverlays();
for (const auto& keyboardLayer: _keyboardLayers) {
for (auto iter = keyboardLayer.begin(); iter != keyboardLayer.end(); iter++) {
overlays.deleteOverlay(iter.key());
}
}
overlays.deleteOverlay(_anchor.overlayID);
overlays.deleteOverlay(_textDisplay.overlayID);
_keyboardLayers.clear();
_ignoreItemsLock.withWriteLock([&] {
_itemsToIgnore.clear();
});
}
void Keyboard::enableStylus() {
auto pointerManager = DependencyManager::get<PointerManager>();
pointerManager->setRenderState(_leftHandStylus, "events on");
pointerManager->enablePointer(_leftHandStylus);
pointerManager->setRenderState(_rightHandStylus, "events on");
pointerManager->enablePointer(_rightHandStylus);
}