//// 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 "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.2f;static const float MALLET_TOUCH_Y_OFFSET = 0.052f;static const float MALLET_Y_OFFSET = 0.180f; static const glm::quat MALLET_ROTATION_OFFSET{0.70710678f, 0.0f, -0.70710678f, 0.0f};static const glm::vec3 MALLET_MODEL_DIMENSIONS{0.03f, MALLET_LENGTH, 0.03f};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.28f, -0.3f, -0.05f};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/keyboard_key.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(); 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); });} 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::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(); 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::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.1f; AudioInjector::playSound(_keySound->getByteArray(), 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; Lines 1–500 of 882. Load more
// // 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 "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.2f; static const float MALLET_TOUCH_Y_OFFSET = 0.052f; static const float MALLET_Y_OFFSET = 0.180f; static const glm::quat MALLET_ROTATION_OFFSET{0.70710678f, 0.0f, -0.70710678f, 0.0f}; static const glm::vec3 MALLET_MODEL_DIMENSIONS{0.03f, MALLET_LENGTH, 0.03f}; 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.28f, -0.3f, -0.05f}; 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/keyboard_key.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(); 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); }); } 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::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(); 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::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.1f; AudioInjector::playSound(_keySound->getByteArray(), 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;