diff --git a/interface/resources/config/keyboard.json b/interface/resources/config/keyboard.json index b3688ef06e..e16c6156ab 100644 --- a/interface/resources/config/keyboard.json +++ b/interface/resources/config/keyboard.json @@ -1,4 +1,22 @@ { + "backPlate": { + "dimensions": { + "x": 0.723600000888109207, + "y": 0.022600000724196434, + "z": 0.2474999976158142 + }, + "position": { + "x": -0.3292800903320312, + "y": 0.004300000742077827, + "z": -0.055427663803100586 + }, + "rotation": { + "w": 1.000, + "x": 0.000, + "y": 0.000, + "z": 0.000 + } + }, "anchor": { "dimensions": { "x": 0.023600000888109207, diff --git a/interface/resources/qml/dialogs/PreferencesDialog.qml b/interface/resources/qml/dialogs/PreferencesDialog.qml index 9df1d0b963..63fde5ec64 100644 --- a/interface/resources/qml/dialogs/PreferencesDialog.qml +++ b/interface/resources/qml/dialogs/PreferencesDialog.qml @@ -70,6 +70,15 @@ ScrollingWindow { } } + var useKeyboardPreference = findPreference("User Interface", "Use Virtual Keyboard"); + var keyboardInputPreference = findPreference("User Interface", "Keyboard laser / mallets"); + if (useKeyboardPreference && keyboardInputPreference) { + keyboardInputPreference.visible = useKeyboardPreference.value; + useKeyboardPreference.valueChanged.connect(function() { + keyboardInputPreference.visible = useKeyboardPreference.value; + }); + } + if (sections.length) { // Default sections to expanded/collapsed as appropriate for dialog. if (sections.length === 1) { @@ -112,4 +121,32 @@ ScrollingWindow { onClicked: root.restoreAll() } } + + function findPreference(category, name) { + var section = null; + var preference = null; + var i; + + // Find category section. + i = 0; + while (!section && i < sections.length) { + if (sections[i].name === category) { + section = sections[i]; + } + i++; + } + + // Find named preference. + if (section) { + i = 0; + while (!preference && i < section.preferences.length) { + if (section.preferences[i].preference && section.preferences[i].preference.name === name) { + preference = section.preferences[i]; + } + i++; + } + } + + return preference; + } } diff --git a/interface/resources/qml/dialogs/preferences/CheckBoxPreference.qml b/interface/resources/qml/dialogs/preferences/CheckBoxPreference.qml index f6f840bbe8..0791a491ff 100644 --- a/interface/resources/qml/dialogs/preferences/CheckBoxPreference.qml +++ b/interface/resources/qml/dialogs/preferences/CheckBoxPreference.qml @@ -16,9 +16,10 @@ import controlsUit 1.0 Preference { id: root height: spacer.height + Math.max(hifi.dimensions.controlLineHeight, checkBox.implicitHeight) - + property bool value: false Component.onCompleted: { checkBox.checked = preference.value; + value = checkBox.checked; preference.value = Qt.binding(function(){ return checkBox.checked; }); } @@ -47,6 +48,7 @@ Preference { onClicked: { Tablet.playSound(TabletEnums.ButtonClick); + value = checked; } anchors { diff --git a/interface/resources/qml/dialogs/preferences/RadioButtonsPreference.qml b/interface/resources/qml/dialogs/preferences/RadioButtonsPreference.qml index 0a09d8d609..1e7d92a138 100644 --- a/interface/resources/qml/dialogs/preferences/RadioButtonsPreference.qml +++ b/interface/resources/qml/dialogs/preferences/RadioButtonsPreference.qml @@ -20,6 +20,11 @@ Preference { property int value: 0 + readonly property int visibleBottomPadding: 3 + readonly property int invisibleBottomPadding: 0 + readonly property int indentLeftMargin: 20 + readonly property int nonindentLeftMargin: 0 + Component.onCompleted: { value = preference.value; repeater.itemAt(preference.value).checked = true; @@ -46,24 +51,24 @@ Preference { preference.save(); } + RalewaySemiBold { + id: heading + size: hifi.fontSizes.inputLabel + text: preference.heading + color: hifi.colors.lightGrayText + visible: text !== "" + bottomPadding: heading.visible ? visibleBottomPadding : invisibleBottomPadding + } + Column { id: control anchors { left: parent.left right: parent.right - bottom: parent.bottom + top: heading.visible ? heading.bottom : heading.top + leftMargin: preference.indented ? indentLeftMargin : nonindentLeftMargin } spacing: 3 - - RalewaySemiBold { - id: heading - size: hifi.fontSizes.inputLabel - text: preference.heading - color: hifi.colors.lightGrayText - visible: text !== "" - bottomPadding: 3 - } - Repeater { id: repeater model: preference.items.length diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml b/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml index a5d7b23df6..d526c9a3cd 100644 --- a/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml +++ b/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml @@ -138,6 +138,15 @@ Item { } } + var useKeyboardPreference = findPreference("User Interface", "Use Virtual Keyboard"); + var keyboardInputPreference = findPreference("User Interface", "Keyboard laser / mallets"); + if (useKeyboardPreference && keyboardInputPreference) { + keyboardInputPreference.visible = useKeyboardPreference.value; + useKeyboardPreference.valueChanged.connect(function() { + keyboardInputPreference.visible = useKeyboardPreference.value; + }); + } + if (sections.length) { // Default sections to expanded/collapsed as appropriate for dialog. if (sections.length === 1) { diff --git a/interface/resources/sounds/keySound.mp3 b/interface/resources/sounds/keySound.mp3 new file mode 100644 index 0000000000..87c7b00807 Binary files /dev/null and b/interface/resources/sounds/keySound.mp3 differ diff --git a/interface/src/scripting/KeyboardScriptingInterface.cpp b/interface/src/scripting/KeyboardScriptingInterface.cpp index d86bb56e64..ccf123efed 100644 --- a/interface/src/scripting/KeyboardScriptingInterface.cpp +++ b/interface/src/scripting/KeyboardScriptingInterface.cpp @@ -12,7 +12,7 @@ #include "KeyboardScriptingInterface.h" #include "ui/Keyboard.h" -bool KeyboardScriptingInterface::isRaised() { +bool KeyboardScriptingInterface::isRaised() const { return DependencyManager::get()->isRaised(); } @@ -20,8 +20,7 @@ void KeyboardScriptingInterface::setRaised(bool raised) { DependencyManager::get()->setRaised(raised); } - -bool KeyboardScriptingInterface::isPassword() { +bool KeyboardScriptingInterface::isPassword() const { return DependencyManager::get()->isPassword(); } @@ -33,6 +32,38 @@ void KeyboardScriptingInterface::loadKeyboardFile(const QString& keyboardFile) { DependencyManager::get()->loadKeyboardFile(keyboardFile); } -bool KeyboardScriptingInterface::getUse3DKeyboard() { +bool KeyboardScriptingInterface::getUse3DKeyboard() const { return DependencyManager::get()->getUse3DKeyboard(); } + +void KeyboardScriptingInterface::disableRightMallet() { + DependencyManager::get()->disableRightMallet(); +} + +void KeyboardScriptingInterface::disableLeftMallet() { + DependencyManager::get()->disableLeftMallet(); +} + +void KeyboardScriptingInterface::enableRightMallet() { + DependencyManager::get()->enableRightMallet(); +} + +void KeyboardScriptingInterface::enableLeftMallet() { + DependencyManager::get()->enableLeftMallet(); +} + +void KeyboardScriptingInterface::setLeftHandLaser(unsigned int leftHandLaser) { + DependencyManager::get()->setLeftHandLaser(leftHandLaser); +} + +void KeyboardScriptingInterface::setRightHandLaser(unsigned int rightHandLaser) { + DependencyManager::get()->setRightHandLaser(rightHandLaser); +} + +bool KeyboardScriptingInterface::getPreferMalletsOverLasers() const { + return DependencyManager::get()->getPreferMalletsOverLasers(); +} + +bool KeyboardScriptingInterface::containsID(OverlayID overlayID) const { + return DependencyManager::get()->containsID(overlayID); +} diff --git a/interface/src/scripting/KeyboardScriptingInterface.h b/interface/src/scripting/KeyboardScriptingInterface.h index 709dfe01de..52d9e821bb 100644 --- a/interface/src/scripting/KeyboardScriptingInterface.h +++ b/interface/src/scripting/KeyboardScriptingInterface.h @@ -15,6 +15,7 @@ #include #include "DependencyManager.h" +#include "ui/overlays/Overlay.h" /**jsdoc * The Keyboard API provides facilities to use 3D Physical keyboard. @@ -26,21 +27,33 @@ * @property {bool} raised - true If the keyboard is visible false otherwise * @property {bool} password - true Will show * instead of characters in the text display false otherwise */ + class KeyboardScriptingInterface : public QObject, public Dependency { Q_OBJECT Q_PROPERTY(bool raised READ isRaised WRITE setRaised) Q_PROPERTY(bool password READ isPassword WRITE setPassword) - Q_PROPERTY(bool use3DKeyboard READ getUse3DKeyboard); + Q_PROPERTY(bool use3DKeyboard READ getUse3DKeyboard CONSTANT); + Q_PROPERTY(bool preferMalletsOverLasers READ getPreferMalletsOverLasers CONSTANT) public: + KeyboardScriptingInterface() = default; + ~KeyboardScriptingInterface() = default; Q_INVOKABLE void loadKeyboardFile(const QString& string); + Q_INVOKABLE void enableLeftMallet(); + Q_INVOKABLE void enableRightMallet(); + Q_INVOKABLE void disableLeftMallet(); + Q_INVOKABLE void disableRightMallet(); + Q_INVOKABLE void setLeftHandLaser(unsigned int leftHandLaser); + Q_INVOKABLE void setRightHandLaser(unsigned int rightHandLaser); + Q_INVOKABLE bool containsID(OverlayID overlayID) const; private: - bool isRaised(); + bool getPreferMalletsOverLasers() const; + bool isRaised() const; void setRaised(bool raised); - bool isPassword(); + bool isPassword() const; void setPassword(bool password); - bool getUse3DKeyboard(); + bool getUse3DKeyboard() const; }; #endif diff --git a/interface/src/ui/Keyboard.cpp b/interface/src/ui/Keyboard.cpp index d647851a80..40894f1121 100644 --- a/interface/src/ui/Keyboard.cpp +++ b/interface/src/ui/Keyboard.cpp @@ -349,6 +349,12 @@ void Keyboard::raiseKeyboardAnchor(bool raise) const { }; overlays.editOverlay(_textDisplay.overlayID, textDisplayProperties); + + auto backPlateOverlay = std::dynamic_pointer_cast(overlays.getOverlay(_backPlate.overlayID)); + + if (backPlateOverlay) { + backPlateOverlay->setVisible(raise); + } } } @@ -380,6 +386,17 @@ void Keyboard::scaleKeyboard(float sensorToWorldScale) { }; overlays.editOverlay(_textDisplay.overlayID, textDisplayProperties); + + + glm::vec3 backPlateScaledDimensions = _backPlate.dimensions * sensorToWorldScale; + glm::vec3 backPlateScaledLocalPosition = _backPlate.localPosition * sensorToWorldScale; + + QVariantMap backPlateProperties { + { "localPosition", vec3toVariant(backPlateScaledLocalPosition) }, + { "dimensions", vec3toVariant(backPlateScaledDimensions) } + }; + + overlays.editOverlay(_backPlate.overlayID, backPlateProperties); } void Keyboard::startLayerSwitchTimer() { @@ -425,6 +442,18 @@ void Keyboard::setPassword(bool password) { updateTextDisplay(); } +void Keyboard::setPreferMalletsOverLasers(bool preferMalletsOverLasers) { + _preferMalletsOverLasersSettingLock.withWriteLock([&] { + _preferMalletsOverLasers.set(preferMalletsOverLasers); + }); +} + +bool Keyboard::getPreferMalletsOverLasers() const { + return _preferMalletsOverLasersSettingLock.resultWithReadLock([&] { + return _preferMalletsOverLasers.get(); + }); +} + void Keyboard::switchToLayer(int layerIndex) { if (layerIndex >= 0 && layerIndex < (int)_keyboardLayers.size()) { Overlays& overlays = qApp->getOverlays(); @@ -459,15 +488,22 @@ void Keyboard::switchToLayer(int layerIndex) { } } +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); + qDebug() << isLaserEvent; + return ((isStylusEvent && preferMalletsOverLasers) || (isLaserEvent && !preferMalletsOverLasers)); +} + void Keyboard::handleTriggerBegin(const OverlayID& overlayID, const PointerEvent& event) { - if (_keyboardLayers.empty() || !isLayerSwitchTimerFinished()) { + if (_keyboardLayers.empty() || !isLayerSwitchTimerFinished() || overlayID == _backPlate.overlayID) { return; } - auto pointerID = event.getID(); auto buttonType = event.getButton(); - - if ((pointerID != _leftHandStylus && pointerID != _rightHandStylus) || buttonType != PointerEvent::PrimaryButton) { + if (!shouldProcessPointerEvent(event) || buttonType != PointerEvent::PrimaryButton) { return; } @@ -481,8 +517,10 @@ void Keyboard::handleTriggerBegin(const OverlayID& overlayID, const PointerEvent 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 handIndex = (pointerID == _leftHandStylus) ? controller::Hand::LEFT : controller::Hand::RIGHT; auto userInputMapper = DependencyManager::get(); userInputMapper->triggerHapticPulse(PULSE_STRENGTH, PULSE_DURATION, handIndex); @@ -550,19 +588,32 @@ void Keyboard::handleTriggerBegin(const OverlayID& overlayID, const PointerEvent QCoreApplication::postEvent(QCoreApplication::instance(), pressEvent); QCoreApplication::postEvent(QCoreApplication::instance(), releaseEvent); - key.startTimer(KEY_PRESS_TIMEOUT_MS); + if (!getPreferMalletsOverLasers()) { + key.startTimer(KEY_PRESS_TIMEOUT_MS); + } auto selection = DependencyManager::get(); selection->addToSelectedItemsList(KEY_PRESSED_HIGHLIGHT, "overlay", overlayID); } } +void Keyboard::setLeftHandLaser(unsigned int leftHandLaser) { + _handLaserLock.withWriteLock([&] { + _leftHandLaser = leftHandLaser; + }); +} + +void Keyboard::setRightHandLaser(unsigned int rightHandLaser) { + _handLaserLock.withWriteLock([&] { + _rightHandLaser = rightHandLaser; + }); +} + void Keyboard::handleTriggerEnd(const OverlayID& overlayID, const PointerEvent& event) { - if (_keyboardLayers.empty() || !isLayerSwitchTimerFinished()) { + if (_keyboardLayers.empty() || !isLayerSwitchTimerFinished() || overlayID == _backPlate.overlayID) { return; } - auto pointerID = event.getID(); - if (pointerID != _leftHandStylus && pointerID != _rightHandStylus) { + if (!shouldProcessPointerEvent(event)) { return; } @@ -582,7 +633,7 @@ void Keyboard::handleTriggerEnd(const OverlayID& overlayID, const PointerEvent& } key.setIsPressed(false); - if (key.timerFinished()) { + if (key.timerFinished() && getPreferMalletsOverLasers()) { key.startTimer(KEY_PRESS_TIMEOUT_MS); } @@ -591,13 +642,11 @@ void Keyboard::handleTriggerEnd(const OverlayID& overlayID, const PointerEvent& } void Keyboard::handleTriggerContinue(const OverlayID& overlayID, const PointerEvent& event) { - if (_keyboardLayers.empty() || !isLayerSwitchTimerFinished()) { + if (_keyboardLayers.empty() || !isLayerSwitchTimerFinished() || overlayID == _backPlate.overlayID) { return; } - auto pointerID = event.getID(); - - if (pointerID != _leftHandStylus && pointerID != _rightHandStylus) { + if (!shouldProcessPointerEvent(event)) { return; } @@ -611,10 +660,11 @@ void Keyboard::handleTriggerContinue(const OverlayID& overlayID, const PointerEv Key& key = search.value(); Overlays& overlays = qApp->getOverlays(); - if (!key.isPressed()) { + if (!key.isPressed() && getPreferMalletsOverLasers()) { auto base3DOverlay = std::dynamic_pointer_cast(overlays.getOverlay(overlayID)); if (base3DOverlay) { + unsigned int pointerID = event.getID(); auto pointerManager = DependencyManager::get(); auto pickResult = pointerManager->getPrevPickResult(pointerID); auto stylusPickResult = std::dynamic_pointer_cast(pickResult); @@ -635,13 +685,11 @@ void Keyboard::handleTriggerContinue(const OverlayID& overlayID, const PointerEv } void Keyboard::handleHoverBegin(const OverlayID& overlayID, const PointerEvent& event) { - if (_keyboardLayers.empty() || !isLayerSwitchTimerFinished()) { + if (_keyboardLayers.empty() || !isLayerSwitchTimerFinished() || overlayID == _backPlate.overlayID) { return; } - auto pointerID = event.getID(); - - if (pointerID != _leftHandStylus && pointerID != _rightHandStylus) { + if (!shouldProcessPointerEvent(event)) { return; } @@ -657,13 +705,11 @@ void Keyboard::handleHoverBegin(const OverlayID& overlayID, const PointerEvent& } void Keyboard::handleHoverEnd(const OverlayID& overlayID, const PointerEvent& event) { - if (_keyboardLayers.empty() || !isLayerSwitchTimerFinished()) { + if (_keyboardLayers.empty() || !isLayerSwitchTimerFinished() || overlayID == _backPlate.overlayID) { return; } - auto pointerID = event.getID(); - - if (pointerID != _leftHandStylus && pointerID != _rightHandStylus) { + if (!shouldProcessPointerEvent(event)) { return; } @@ -755,6 +801,27 @@ void Keyboard::loadKeyboardFile(const QString& keyboardFile) { anchor.originalDimensions = dimensions; _anchor = anchor; + QJsonObject backPlateObject = jsonObject["backPlate"].toObject(); + + QVariantMap backPlateProperties { + { "name", "backPlate"}, + { "isSolid", true }, + { "visible", true }, + { "grabbable", false }, + { "alpha", 0.0 }, + { "ignoreRayIntersection", false}, + { "dimensions", backPlateObject["dimensions"].toVariant() }, + { "position", backPlateObject["position"].toVariant() }, + { "orientation", backPlateObject["rotation"].toVariant() }, + { "parentID", _anchor.overlayID } + }; + + BackPlate backPlate; + backPlate.overlayID = overlays.addOverlay("cube", backPlateProperties); + backPlate.dimensions = vec3FromVariant(backPlateObject["dimensions"].toVariant()); + backPlate.localPosition = vec3FromVariant(overlays.getProperty(backPlate.overlayID, "localPosition").value); + _backPlate = backPlate; + const QJsonArray& keyboardLayers = jsonObject["layers"].toArray(); int keyboardLayerCount = keyboardLayers.size(); _keyboardLayers.reserve(keyboardLayerCount); @@ -878,6 +945,7 @@ void Keyboard::clearKeyboardKeys() { overlays.deleteOverlay(_anchor.overlayID); overlays.deleteOverlay(_textDisplay.overlayID); + overlays.deleteOverlay(_backPlate.overlayID); _keyboardLayers.clear(); @@ -887,10 +955,42 @@ void Keyboard::clearKeyboardKeys() { } void Keyboard::enableStylus() { - auto pointerManager = DependencyManager::get(); - pointerManager->setRenderState(_leftHandStylus, "events on"); - pointerManager->enablePointer(_leftHandStylus); - pointerManager->setRenderState(_rightHandStylus, "events on"); - pointerManager->enablePointer(_rightHandStylus); + if (getPreferMalletsOverLasers()) { + auto pointerManager = DependencyManager::get(); + pointerManager->setRenderState(_leftHandStylus, "events on"); + pointerManager->enablePointer(_leftHandStylus); + pointerManager->setRenderState(_rightHandStylus, "events on"); + pointerManager->enablePointer(_rightHandStylus); + } } + +void Keyboard::enableRightMallet() { + auto pointerManager = DependencyManager::get(); + pointerManager->setRenderState(_rightHandStylus, "events on"); + pointerManager->enablePointer(_rightHandStylus); +} + +void Keyboard::enableLeftMallet() { + auto pointerManager = DependencyManager::get(); + pointerManager->setRenderState(_leftHandStylus, "events on"); + pointerManager->enablePointer(_leftHandStylus); +} + +void Keyboard::disableLeftMallet() { + auto pointerManager = DependencyManager::get(); + pointerManager->setRenderState(_leftHandStylus, "events off"); + pointerManager->disablePointer(_leftHandStylus); +} + +void Keyboard::disableRightMallet() { + auto pointerManager = DependencyManager::get(); + pointerManager->setRenderState(_rightHandStylus, "events off"); + pointerManager->disablePointer(_rightHandStylus); +} + +bool Keyboard::containsID(OverlayID overlayID) const { + return resultWithReadLock([&] { + return _itemsToIgnore.contains(overlayID) || _backPlate.overlayID == overlayID; + }); +} diff --git a/interface/src/ui/Keyboard.h b/interface/src/ui/Keyboard.h index 9c0c8c40f2..b917b60eb4 100644 --- a/interface/src/ui/Keyboard.h +++ b/interface/src/ui/Keyboard.h @@ -87,7 +87,8 @@ private: std::shared_ptr _timer { std::make_shared() }; }; -class Keyboard : public Dependency, public QObject, public ReadWriteLockable { +class Keyboard : public QObject, public Dependency, public ReadWriteLockable { + Q_OBJECT public: Keyboard(); void createKeyboard(); @@ -97,9 +98,20 @@ public: bool isPassword() const; void setPassword(bool password); + void enableRightMallet(); + void enableLeftMallet(); + void disableRightMallet(); + void disableLeftMallet(); + + void setLeftHandLaser(unsigned int leftHandLaser); + void setRightHandLaser(unsigned int rightHandLaser); + + void setPreferMalletsOverLasers(bool preferMalletsOverLasers); + bool getPreferMalletsOverLasers() const; bool getUse3DKeyboard() const; void setUse3DKeyboard(bool use); + bool containsID(OverlayID overlayID) const; void loadKeyboardFile(const QString& keyboardFile); QVector getKeysID(); @@ -118,6 +130,12 @@ private: glm::vec3 originalDimensions; }; + struct BackPlate { + OverlayID overlayID; + glm::vec3 dimensions; + glm::vec3 localPosition; + }; + struct TextDisplay { float lineHeight; OverlayID overlayID; @@ -127,10 +145,10 @@ private: void raiseKeyboard(bool raise) const; void raiseKeyboardAnchor(bool raise) const; - - void setLayerIndex(int layerIndex); void enableStylus(); void disableStylus(); + + void setLayerIndex(int layerIndex); void clearKeyboardKeys(); void switchToLayer(int layerIndex); void updateTextDisplay(); @@ -138,23 +156,31 @@ private: void startLayerSwitchTimer(); bool isLayerSwitchTimerFinished(); + bool shouldProcessPointerEvent(const PointerEvent& event) const; + bool _raised { false }; bool _password { false }; bool _capsEnabled { false }; int _layerIndex { 0 }; + Setting::Handle _preferMalletsOverLasers { "preferMalletsOverLaser", true }; unsigned int _leftHandStylus { 0 }; unsigned int _rightHandStylus { 0 }; + unsigned int _leftHandLaser { 0 }; + unsigned int _rightHandLaser { 0 }; SharedSoundPointer _keySound { nullptr }; std::shared_ptr _layerSwitchTimer { std::make_shared() }; mutable ReadWriteLockable _use3DKeyboardLock; + mutable ReadWriteLockable _handLaserLock; + mutable ReadWriteLockable _preferMalletsOverLasersSettingLock; + mutable ReadWriteLockable _ignoreItemsLock; Setting::Handle _use3DKeyboard { "use3DKeyboard", true }; QString _typedCharacters; TextDisplay _textDisplay; Anchor _anchor; + BackPlate _backPlate; - mutable ReadWriteLockable _ignoreItemsLock; QVector _itemsToIgnore; std::vector> _keyboardLayers; }; diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index d1fbe02759..5bb1945c64 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -107,12 +107,6 @@ void setupPreferences() { preferences->addPreference(preference); } - { - auto getter = []()->bool { return qApp->getPreferStylusOverLaser(); }; - auto setter = [](bool value) { qApp->setPreferStylusOverLaser(value); }; - preferences->addPreference(new CheckPreference(UI_CATEGORY, "Prefer Stylus Over Laser", getter, setter)); - } - { static const QString RETICLE_ICON_NAME = { Cursor::Manager::getIconName(Cursor::Icon::RETICLE) }; auto getter = []()->bool { return qApp->getPreferredCursor() == RETICLE_ICON_NAME; }; @@ -121,15 +115,38 @@ void setupPreferences() { } { - auto getter = []()->bool { return DependencyManager::get()->getUse3DKeyboard(); }; - auto setter = [](bool value) { DependencyManager::get()->setUse3DKeyboard(value); }; + auto getter = []()->bool { return qApp->getMiniTabletEnabled(); }; + auto setter = [](bool value) { qApp->setMiniTabletEnabled(value); }; + preferences->addPreference(new CheckPreference(UI_CATEGORY, "Use mini tablet", getter, setter)); + } + + { + auto getter = []()->int { return DependencyManager::get()->getUse3DKeyboard(); }; + auto setter = [](int value) { DependencyManager::get()->setUse3DKeyboard(value); }; preferences->addPreference(new CheckPreference(UI_CATEGORY, "Use Virtual Keyboard", getter, setter)); } { - auto getter = []()->bool { return qApp->getMiniTabletEnabled(); }; - auto setter = [](bool value) { qApp->setMiniTabletEnabled(value); }; - preferences->addPreference(new CheckPreference(UI_CATEGORY, "Use mini tablet", getter, setter)); + auto getter = []()->bool { return DependencyManager::get()->getPreferMalletsOverLasers() ? 1 : 0; }; + auto setter = [](bool value) { return DependencyManager::get()->setPreferMalletsOverLasers((bool)value); }; + auto preference = new RadioButtonsPreference(UI_CATEGORY, "Keyboard laser / mallets", getter, setter); + QStringList items; + items << "Lasers" << "Mallets"; + preference->setItems(items); + preference->setIndented(true); + preferences->addPreference(preference); + } + + + { + auto getter = []()->int { return qApp->getPreferStylusOverLaser() ? 1 : 0; }; + auto setter = [](int value) { qApp->setPreferStylusOverLaser((bool)value); }; + auto preference = new RadioButtonsPreference(UI_CATEGORY, "Tablet stylys / laser", getter, setter); + QStringList items; + items << "Lasers" << "Stylus"; + preference->setHeading("Tablet Input Mechanism"); + preference->setItems(items); + preferences->addPreference(preference); } static const QString VIEW_CATEGORY{ "View" }; @@ -151,15 +168,14 @@ void setupPreferences() { preferences->addPreference(preference); } - - // FIXME: Remove setting completely or make available through JavaScript API? /* + // FIXME: Remove setting completely or make available through JavaScript API? { auto getter = []()->bool { return qApp->getPreferAvatarFingerOverStylus(); }; auto setter = [](bool value) { qApp->setPreferAvatarFingerOverStylus(value); }; preferences->addPreference(new CheckPreference(UI_CATEGORY, "Prefer Avatar Finger Over Stylus", getter, setter)); - } - */ + }*/ + // Snapshots static const QString SNAPSHOTS { "Snapshots" }; { diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index 19bdfce2b3..805832760e 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -556,6 +556,7 @@ void ModelOverlay::locationChanged(bool tellPhysics) { if (_model && _model->isActive()) { _model->setRotation(getWorldOrientation()); _model->setTranslation(getWorldPosition()); + _updateModel = true; } } diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index 17b0895f47..754c8d26a9 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -539,7 +539,7 @@ RayToOverlayIntersectionResult Overlays::findRayIntersectionVector(const PickRay bool bestIsFront = false; bool bestIsTablet = false; auto tabletIDs = qApp->getTabletIDs(); - const QVector keyboardKeysToDiscard = DependencyManager::get()->getKeysID(); + QMutexLocker locker(&_mutex); RayToOverlayIntersectionResult result; QMapIterator i(_overlaysWorld); @@ -549,8 +549,7 @@ RayToOverlayIntersectionResult Overlays::findRayIntersectionVector(const PickRay auto thisOverlay = std::dynamic_pointer_cast(i.value()); if ((overlaysToDiscard.size() > 0 && overlaysToDiscard.contains(thisID)) || - (overlaysToInclude.size() > 0 && !overlaysToInclude.contains(thisID)) || - (keyboardKeysToDiscard.size() > 0 && keyboardKeysToDiscard.contains(thisID))) { + (overlaysToInclude.size() > 0 && !overlaysToInclude.contains(thisID))) { continue; } diff --git a/libraries/shared/src/Preferences.h b/libraries/shared/src/Preferences.h index 4a58d71c54..0fec8708e8 100644 --- a/libraries/shared/src/Preferences.h +++ b/libraries/shared/src/Preferences.h @@ -364,6 +364,7 @@ class RadioButtonsPreference : public IntPreference { Q_OBJECT Q_PROPERTY(QString heading READ getHeading CONSTANT) Q_PROPERTY(QStringList items READ getItems CONSTANT) + Q_PROPERTY(bool indented READ getIndented CONSTANT) public: RadioButtonsPreference(const QString& category, const QString& name, Getter getter, Setter setter) : IntPreference(category, name, getter, setter) { } @@ -373,10 +374,13 @@ public: const QStringList& getItems() { return _items; } void setHeading(const QString& heading) { _heading = heading; } void setItems(const QStringList& items) { _items = items; } + bool getIndented() { return _indented; } + void setIndented(const bool indented) { _indented = indented; } protected: QString _heading; QStringList _items; + bool _indented { false }; }; #endif diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index 2658f11989..a579fee0d9 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -12,7 +12,7 @@ LEFT_HAND, RIGHT_HAND, NEAR_GRAB_PICK_RADIUS, DEFAULT_SEARCH_SPHERE_DISTANCE, DISPATCHER_PROPERTIES, getGrabPointSphereOffset, HMD, MyAvatar, Messages, findHandChildEntities, Picks, PickType, Pointers, PointerManager, getGrabPointSphereOffset, HMD, MyAvatar, Messages, findHandChildEntities, Picks, PickType, Pointers, - PointerManager, print, Selection, DISPATCHER_HOVERING_LIST, DISPATCHER_HOVERING_STYLE + PointerManager, print, Selection, DISPATCHER_HOVERING_LIST, DISPATCHER_HOVERING_STYLE, Keyboard */ controllerDispatcherPlugins = {}; @@ -453,6 +453,8 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); distanceScaleEnd: true, hand: LEFT_HAND }); + + Keyboard.setLeftHandLaser(this.leftPointer); this.rightPointer = this.pointerManager.createPointer(false, PickType.Ray, { joint: "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND", filter: Picks.PICK_OVERLAYS | Picks.PICK_ENTITIES | Picks.PICK_INCLUDE_NONCOLLIDABLE, @@ -463,6 +465,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); distanceScaleEnd: true, hand: RIGHT_HAND }); + Keyboard.setRightHandLaser(this.rightPointer); this.leftHudPointer = this.pointerManager.createPointer(true, PickType.Ray, { joint: "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", filter: Picks.PICK_HUD, diff --git a/scripts/system/controllers/controllerModules/stylusInput.js b/scripts/system/controllers/controllerModules/stylusInput.js index 57066fb2dd..c4aa9efd50 100644 --- a/scripts/system/controllers/controllerModules/stylusInput.js +++ b/scripts/system/controllers/controllerModules/stylusInput.js @@ -128,7 +128,7 @@ Script.include("/~/system/libraries/controllers.js"); } } - var WEB_DISPLAY_STYLUS_DISTANCE = 0.5; + const WEB_DISPLAY_STYLUS_DISTANCE = (Keyboard.raised && Keyboard.preferMalletsOverLasers) ? 0.2 : 0.5; var nearStylusTarget = isNearStylusTarget(stylusTargets, WEB_DISPLAY_STYLUS_DISTANCE * sensorScaleFactor); if (nearStylusTarget.length !== 0) { @@ -152,9 +152,13 @@ Script.include("/~/system/libraries/controllers.js"); if (isUsingStylus && this.processStylus(controllerData)) { Pointers.enablePointer(this.pointer); + this.hand === RIGHT_HAND ? Keyboard.disableRightMallet() : Keyboard.disableLeftMallet(); return makeRunningValues(true, [], []); } else { Pointers.disablePointer(this.pointer); + if (Keyboard.raised && Keyboard.preferMalletsOverLasers) { + this.hand === RIGHT_HAND ? Keyboard.enableRightMallet() : Keyboard.enableLeftMallet(); + } return makeRunningValues(false, [], []); } }; diff --git a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js index d2cb7fffd1..c1b0658af2 100644 --- a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js +++ b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js @@ -14,11 +14,21 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); Script.include("/~/system/libraries/controllers.js"); (function() { + const intersectionType = { + None: 0, + WebOverlay: 1, + WebEntity: 2, + HifiKeyboard: 3, + Overlay: 4, + HifiTablet: 5, + }; + function WebSurfaceLaserInput(hand) { this.hand = hand; this.otherHand = this.hand === RIGHT_HAND ? LEFT_HAND : RIGHT_HAND; this.running = false; this.ignoredObjects = []; + this.intersectedType = intersectionType["None"]; this.parameters = makeDispatcherModuleParameters( 160, @@ -124,18 +134,29 @@ Script.include("/~/system/libraries/controllers.js"); if ((HMD.tabletID && objectID === HMD.tabletID) || (HMD.tabletScreenID && objectID === HMD.tabletScreenID) || (HMD.homeButtonID && objectID === HMD.homeButtonID)) { - return true; + return intersectionType["HifiTablet"]; } else { var overlayType = Overlays.getOverlayType(objectID); - return overlayType === "web3d" || triggerPressed; + var type = intersectionType["None"]; + if (Keyboard.containsID(objectID) && !Keyboard.preferMalletsOverLasers) { + type = intersectionType["HifiKeyboard"]; + } else if (overlayType === "web3d") { + type = intersectionType["WebOverlay"]; + } else if (triggerPressed) { + type = intersectionType["Overlay"]; + } + + return type; } } else if (intersection.type === Picks.INTERSECTED_ENTITY) { var entityProperties = Entities.getEntityProperties(objectID, DISPATCHER_PROPERTIES); var entityType = entityProperties.type; var isLocked = entityProperties.locked; - return entityType === "Web" && (!isLocked || triggerPressed); + if (entityType === "Web" && (!isLocked || triggerPressed)) { + return intersectionType["WebEntity"]; + } } - return false; + return intersectionType["None"]; }; this.deleteContextOverlay = function() { @@ -152,9 +173,9 @@ Script.include("/~/system/libraries/controllers.js"); } }; - this.updateAllwaysOn = function() { + this.updateAlwaysOn = function(type) { var PREFER_STYLUS_OVER_LASER = "preferStylusOverLaser"; - this.parameters.handLaser.allwaysOn = !Settings.getValue(PREFER_STYLUS_OVER_LASER, false); + this.parameters.handLaser.allwaysOn = (!Settings.getValue(PREFER_STYLUS_OVER_LASER, false) || type === intersectionType["HifiKeyboard"]); }; this.getDominantHand = function() { @@ -164,18 +185,27 @@ Script.include("/~/system/libraries/controllers.js"); this.dominantHandOverride = false; this.isReady = function(controllerData) { - var otherModuleRunning = this.getOtherModule().running; - otherModuleRunning = otherModuleRunning && this.getDominantHand() !== this.hand; // Auto-swap to dominant hand. var isTriggerPressed = controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE && - controllerData.triggerValues[this.otherHand] <= TRIGGER_OFF_VALUE; - var allowThisModule = !otherModuleRunning || isTriggerPressed; + controllerData.triggerValues[this.otherHand] <= TRIGGER_OFF_VALUE; + var type = this.isPointingAtTriggerable(controllerData, isTriggerPressed, false); - if ((allowThisModule && this.isPointingAtTriggerable(controllerData, isTriggerPressed, false)) && !this.grabModuleWantsNearbyOverlay(controllerData)) { - this.updateAllwaysOn(); - if (isTriggerPressed) { - this.dominantHandOverride = true; // Override dominant hand. - this.getOtherModule().dominantHandOverride = false; + if (type !== intersectionType["None"] && !this.grabModuleWantsNearbyOverlay(controllerData)) { + if (type === intersectionType["WebOverlay"] || type === intersectionType["WebEntity"] || type === intersectionType["HifiTablet"]) { + var otherModuleRunning = this.getOtherModule().running; + otherModuleRunning = otherModuleRunning && this.getDominantHand() !== this.hand; // Auto-swap to dominant hand. + var allowThisModule = !otherModuleRunning || isTriggerPressed; + + if (!allowThisModule) { + return makeRunningValues(true, [], []); + } + + if (isTriggerPressed) { + this.dominantHandOverride = true; // Override dominant hand. + this.getOtherModule().dominantHandOverride = false; + } } + + this.updateAlwaysOn(type); if (this.parameters.handLaser.allwaysOn || isTriggerPressed) { return makeRunningValues(true, [], []); } @@ -187,33 +217,42 @@ Script.include("/~/system/libraries/controllers.js"); return makeRunningValues(false, [], []); }; - this.run = function(controllerData, deltaTime) { + this.shouldThisModuleRun = function(controllerData) { var otherModuleRunning = this.getOtherModule().running; otherModuleRunning = otherModuleRunning && this.getDominantHand() !== this.hand; // Auto-swap to dominant hand. otherModuleRunning = otherModuleRunning || this.getOtherModule().dominantHandOverride; // Override dominant hand. var grabModuleNeedsToRun = this.grabModuleWantsNearbyOverlay(controllerData); // only allow for non-near grab - var allowThisModule = !otherModuleRunning && !grabModuleNeedsToRun; + return !otherModuleRunning && !grabModuleNeedsToRun; + }; + + this.run = function(controllerData, deltaTime) { + this.addObjectToIgnoreList(controllerData); + var type = this.isPointingAtTriggerable(controllerData, isTriggerPressed, false); var isTriggerPressed = controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE; var laserOn = isTriggerPressed || this.parameters.handLaser.allwaysOn; this.addObjectToIgnoreList(controllerData); - if (allowThisModule) { - if (isTriggerPressed && !this.isPointingAtTriggerable(controllerData, isTriggerPressed, true)) { - // if trigger is down + not pointing at a web entity, keep running web surface laser + + if (type === intersectionType["HifiTablet"] && laserOn) { + if (this.shouldThisModuleRun(controllerData)) { this.running = true; return makeRunningValues(true, [], []); - } else if (laserOn && this.isPointingAtTriggerable(controllerData, isTriggerPressed, false)) { - // if trigger is down + pointing at a web entity/overlay, keep running web surface laser - this.running = true; - return makeRunningValues(true, [], []); - } else { - this.deleteContextOverlay(); - this.running = false; - this.dominantHandOverride = false; - return makeRunningValues(false, [], []); } + } else if ((type === intersectionType["WebOverlay"] || type === intersectionType["WebEntity"]) && laserOn) { // auto laser on WebEntities andWebOverlays + if (this.shouldThisModuleRun(controllerData)) { + this.running = true; + return makeRunningValues(true, [], []); + } + } else if ((type === intersectionType["HifiKeyboard"] && laserOn) || type === intersectionType["Overlay"]) { + this.running = true; + return makeRunningValues(true, [], []); + } else if (isTriggerPressed && !this.isPointingAtTriggerable(controllerData, isTriggerPressed, true)) { + // if trigger is down + not pointing at a web entity, keep running web surface laser + this.running = true; + return makeRunningValues(true, [], []); + } - // if module needs to stop from near grabs or other modules are running, stop it. + this.deleteContextOverlay(); this.running = false; this.dominantHandOverride = false;