diff --git a/LICENSE b/LICENSE
index 60e86a1cc7..deb80afc19 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2013-2016, High Fidelity, Inc.
+Copyright (c) 2013-2018, High Fidelity, Inc.
All rights reserved.
licensing@highfidelity.io
diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp
index 4490474599..d9a399c162 100644
--- a/assignment-client/src/Agent.cpp
+++ b/assignment-client/src/Agent.cpp
@@ -216,7 +216,7 @@ void Agent::requestScript() {
}
// make sure this is not a script request for the file scheme
- if (scriptURL.scheme() == URL_SCHEME_FILE) {
+ if (scriptURL.scheme() == HIFI_URL_SCHEME_FILE) {
qWarning() << "Cannot load script for Agent from local filesystem.";
scriptRequestFinished();
return;
diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp
index 7e1420ef60..7f0838d8a5 100644
--- a/assignment-client/src/audio/AudioMixerClientData.cpp
+++ b/assignment-client/src/audio/AudioMixerClientData.cpp
@@ -202,7 +202,7 @@ void AudioMixerClientData::parsePerAvatarGainSet(ReceivedMessage& message, const
}
}
-void AudioMixerClientData::setGainForAvatar(QUuid nodeID, uint8_t gain) {
+void AudioMixerClientData::setGainForAvatar(QUuid nodeID, float gain) {
auto it = std::find_if(_streams.active.cbegin(), _streams.active.cend(), [nodeID](const MixableStream& mixableStream){
return mixableStream.nodeStreamID.nodeID == nodeID && mixableStream.nodeStreamID.streamID.isNull();
});
diff --git a/assignment-client/src/audio/AudioMixerClientData.h b/assignment-client/src/audio/AudioMixerClientData.h
index 610b258789..0a66a8164b 100644
--- a/assignment-client/src/audio/AudioMixerClientData.h
+++ b/assignment-client/src/audio/AudioMixerClientData.h
@@ -172,7 +172,7 @@ private:
void optionallyReplicatePacket(ReceivedMessage& packet, const Node& node);
- void setGainForAvatar(QUuid nodeID, uint8_t gain);
+ void setGainForAvatar(QUuid nodeID, float gain);
bool containsValidPosition(ReceivedMessage& message) const;
diff --git a/interface/resources/config/keyboard.json b/interface/resources/config/keyboard.json
new file mode 100644
index 0000000000..186a9c1084
--- /dev/null
+++ b/interface/resources/config/keyboard.json
@@ -0,0 +1,2476 @@
+{
+ "anchor": {
+ "dimensions": {
+ "x": 0.023600000888109207,
+ "y": 0.022600000724196434,
+ "z": 0.1274999976158142
+ },
+ "position": {
+ "x": 0.006292800903320312,
+ "y": 0.004300000742077827,
+ "z": 0.005427663803100586
+ },
+ "rotation": {
+ "w": 1.000,
+ "x": 0.000,
+ "y": 0.000,
+ "z": 0.000
+ }
+ },
+ "textDisplay": {
+ "dimensions": {
+ "x": 0.15,
+ "y": 0.045,
+ "z": 0.1
+ },
+ "localPosition": {
+ "x": -0.3032040786743164,
+ "y": 0.059300000742077827,
+ "z": 0.06454843521118164
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.906,
+ "z": 0.423
+ },
+ "leftMargin": 0.0,
+ "rightMargin": 0.0,
+ "topMargin": 0.0,
+ "bottomMargin": 0.0,
+ "lineHeight": 0.05
+ },
+ "useResourcesPath": true,
+ "layers": [
+ [
+ {
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.5332040786743164,
+ "y": 0.019300000742077827,
+ "z": 0.03745675086975098
+ },
+ "key": "p",
+ "texture": {
+ "file9": "meshes/keyboard/key_p.png",
+ "file10": "meshes/keyboard/key_p.png"
+ },
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.42067813873291016,
+ "y": 0.019300000742077827,
+ "z": 0.03744244575500488
+ },
+ "key": "i",
+ "texture": {
+ "file9": "meshes/keyboard/key_i.png",
+ "file10": "meshes/keyboard/key_i.png"
+ },
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "o",
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.47769832611083984,
+ "y": 0.019300000742077827,
+ "z": 0.03745675086975098
+ },
+ "texture": {
+ "file9": "meshes/keyboard/key_o.png",
+ "file10": "meshes/keyboard/key_o.png"
+ },
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.49904823303222656,
+ "y": 0.019300000742077827,
+ "z": -0.01745915412902832
+ },
+ "key": "l",
+ "texture": {
+ "file9": "meshes/keyboard/key_l.png",
+ "file10": "meshes/keyboard/key_l.png"
+ },
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.4439973831176758,
+ "y": 0.019300000742077827,
+ "z": -0.01745915412902832
+ },
+ "key": "k",
+ "texture": {
+ "file9": "meshes/keyboard/key_k.png",
+ "file10": "meshes/keyboard/key_k.png"
+ },
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "y",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/key_y.png",
+ "file10": "meshes/keyboard/key_y.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.30902957916259766,
+ "y": 0.019300000742077827,
+ "z": 0.0374448299407959
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "r",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/key_r.png",
+ "file10": "meshes/keyboard/key_r.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.020519999787211418,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.19474029541015625,
+ "y": 0.019300000742077827,
+ "z": 0.03745102882385254
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "d",
+ "texture": {
+ "file9": "meshes/keyboard/key_d.png",
+ "file10": "meshes/keyboard/key_d.png"
+ },
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.16272640228271484,
+ "y": 0.019300000742077827,
+ "z": -0.01747274398803711
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "t",
+ "texture": {
+ "file9": "meshes/keyboard/key_t.png",
+ "file10": "meshes/keyboard/key_t.png"
+ },
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.2517843246459961,
+ "y": 0.019300000742077827,
+ "z": 0.03744622692465782
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "f",
+ "texture": {
+ "file9": "meshes/keyboard/key_f.png",
+ "file10": "meshes/keyboard/key_f.png"
+ },
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.2200756072998047,
+ "y": 0.019300000742077827,
+ "z": -0.01746511459350586
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "g",
+ "texture": {
+ "file9": "meshes/keyboard/key_g.png",
+ "file10": "meshes/keyboard/key_g.png"
+ },
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.27622222900390625,
+ "y": 0.019300000742077827,
+ "z": -0.017457084730267525
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "w",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/key_w.png",
+ "file10": "meshes/keyboard/key_w.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.08203601837158203,
+ "y": 0.019300000742077827,
+ "z": 0.03743100166320801
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "q",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/key_q.png",
+ "file10": "meshes/keyboard/key_q.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.026292800903320312,
+ "y": 0.019300000742077827,
+ "z": 0.037427663803100586
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "a",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/key_a.png",
+ "file10": "meshes/keyboard/key_a.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.050909996032714844,
+ "y": 0.019300000742077827,
+ "z": -0.017487764358520508
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "e",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/key_e.png",
+ "file10": "meshes/keyboard/key_e.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.13773441314697266,
+ "y": 0.019300000742077827,
+ "z": 0.03745269775390625
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "s",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/key_s.png",
+ "file10": "meshes/keyboard/key_s.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.10659980773925781,
+ "y": 0.019300000742077827,
+ "z": -0.017481565475463867
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "u",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/key_u.png",
+ "file10": "meshes/keyboard/key_u.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.36423587799072266,
+ "y": 0.019300000742077827,
+ "z": 0.03743577003479004
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "h",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/key_h.png",
+ "file10": "meshes/keyboard/key_h.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.3321561813354492,
+ "y": 0.019300000742077827,
+ "z": -0.017461776733398438
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "j",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/key_j.png",
+ "file10": "meshes/keyboard/key_j.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.020519999787211418,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.38753604888916016,
+ "y": 0.019300000742077827,
+ "z": -0.01746058464050293
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "c",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/key_c.png",
+ "file10": "meshes/keyboard/key_c.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.22023773193359375,
+ "y": 0.019300000742077827,
+ "z": -0.07282757759094238
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "v",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/key_v.png",
+ "file10": "meshes/keyboard/key_v.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.2767200469970703,
+ "y": 0.019300000742077827,
+ "z": -0.07291850447654724
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "b",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/key_b.png",
+ "file10": "meshes/keyboard/key_b.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.020519999787211418,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.33254528045654297,
+ "y": 0.019300000742077827,
+ "z": -0.07283258438110352
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": " ",
+ "name": "space",
+ "dimensions": {
+ "x": 0.14597539603710175,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.27660465240478516,
+ "y": 0.019300000742077827,
+ "z": -0.12705934047698975
+ },
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/white.png",
+ "file10": "meshes/keyboard/white.png"
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "z",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/key_z.png",
+ "file10": "meshes/keyboard/key_z.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.10669422149658203,
+ "y": 0.019300000742077827,
+ "z": -0.07285571098327637
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "n",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/key_n.png",
+ "file10": "meshes/keyboard/key_n.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.020292000845074654,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.38794422149658203,
+ "y": 0.019300000742077827,
+ "z": -0.0728309154510498
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "m",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/key_m.png",
+ "file10": "meshes/keyboard/key_m.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.01846799999475479,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.44464683532714844,
+ "y": 0.019300000742077827,
+ "z": -0.07282185554504395
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "x",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/key_x.png",
+ "file10": "meshes/keyboard/key_x.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.1630725860595703,
+ "y": 0.019300000742077827,
+ "z": -0.07284045219421387
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "DEL",
+ "type": "backspace",
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.020519999787211418,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.53203323516845703,
+ "y": 0.019300000742077827,
+ "z": -0.07286686894893646
+ },
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/key_backspace.png",
+ "file10": "meshes/keyboard/key_backspace.png"
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "Caps",
+ "type": "caps",
+ "switchToLayer": 1,
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.020519999787211418,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.02603323516845703,
+ "y": 0.019300000742077827,
+ "z": -0.07285571098327637
+ },
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/key_cap.png",
+ "file10": "meshes/keyboard/key_cap.png"
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "Close",
+ "type": "close",
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.020519999787211418,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.59333323516845703,
+ "y": 0.019300000742077827,
+ "z": 0.037454843521118164
+ },
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/key_exit.png",
+ "file10": "meshes/keyboard/key_exit.png"
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "Enter",
+ "type": "enter",
+ "dimensions": {
+ "x": 0.08787999764084816,
+ "z": 0.020519999787211418,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.5103323516845703,
+ "y": 0.019300000742077827,
+ "z": -0.127054843521118164
+ },
+ "modelURL": "meshes/keyboard/SM_enter.fbx",
+ "texture": {
+ "file10": "meshes/keyboard/key_enter.png",
+ "file11": "meshes/keyboard/key_enter.png"
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "numbers",
+ "type": "layer",
+ "switchToLayer": 2,
+ "dimensions": {
+ "x": 0.07787999764084816,
+ "z": 0.020519999787211418,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.026323516845703,
+ "y": 0.019300000742077827,
+ "z": -0.127054843521118164
+ },
+ "modelURL": "meshes/keyboard/SM_enter.fbx",
+ "texture": {
+ "file10": "meshes/keyboard/key_123.png",
+ "file11": "meshes/keyboard/key_123.png"
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ }
+ ],
+ [
+ {
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.5332040786743164,
+ "y": 0.019300000742077827,
+ "z": 0.037454843521118164
+ },
+ "key": "p",
+ "texture": {
+ "file9": "meshes/keyboard/keyCap_p.png",
+ "file10": "meshes/keyboard/keyCap_p.png"
+ },
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.42067813873291016,
+ "y": 0.019300000742077827,
+ "z": 0.03744244575500488
+ },
+ "key": "i",
+ "texture": {
+ "file9": "meshes/keyboard/keyCap_i.png",
+ "file10": "meshes/keyboard/keyCap_i.png"
+ },
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "o",
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.47769832611083984,
+ "y": 0.019300000742077827,
+ "z": 0.03745675086975098
+ },
+ "texture": {
+ "file9": "meshes/keyboard/keyCap_o.png",
+ "file10": "meshes/keyboard/keyCap_o.png"
+ },
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.49904823303222656,
+ "y": 0.019300000742077827,
+ "z": -0.01745915412902832
+ },
+ "key": "l",
+ "texture": {
+ "file9": "meshes/keyboard/keyCap_l.png",
+ "file10": "meshes/keyboard/keyCap_l.png"
+ },
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.4439973831176758,
+ "y": 0.019300000742077827,
+ "z": -0.01745915412902832
+ },
+ "key": "k",
+ "texture": {
+ "file9": "meshes/keyboard/keyCap_k.png",
+ "file10": "meshes/keyboard/keyCap_k.png"
+ },
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "y",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/keyCap_y.png",
+ "file10": "meshes/keyboard/keyCap_y.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.30902957916259766,
+ "y": 0.019300000742077827,
+ "z": 0.0374448299407959
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "r",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/keyCap_r.png",
+ "file10": "meshes/keyboard/keyCap_r.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.020519999787211418,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.19474029541015625,
+ "y": 0.019300000742077827,
+ "z": 0.03745102882385254
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "d",
+ "texture": {
+ "file9": "meshes/keyboard/keyCap_d.png",
+ "file10": "meshes/keyboard/keyCap_d.png"
+ },
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.16272640228271484,
+ "y": 0.019300000742077827,
+ "z": -0.01747274398803711
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "t",
+ "texture": {
+ "file9": "meshes/keyboard/keyCap_t.png",
+ "file10": "meshes/keyboard/keyCap_t.png"
+ },
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.2517843246459961,
+ "y": 0.019300000742077827,
+ "z": 0.03744622692465782
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "f",
+ "texture": {
+ "file9": "meshes/keyboard/keyCap_F.png",
+ "file10": "meshes/keyboard/keyCap_F.png"
+ },
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.2200756072998047,
+ "y": 0.019300000742077827,
+ "z": -0.01746511459350586
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "g",
+ "texture": {
+ "file9": "meshes/keyboard/keyCap_g.png",
+ "file10": "meshes/keyboard/keyCap_g.png"
+ },
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.27622222900390625,
+ "y": 0.019300000742077827,
+ "z": -0.017457084730267525
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "w",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/keyCap_w.png",
+ "file10": "meshes/keyboard/keyCap_w.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.08203601837158203,
+ "y": 0.019300000742077827,
+ "z": 0.03743100166320801
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "q",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/keyCap_q.png",
+ "file10": "meshes/keyboard/keyCap_q.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.026292800903320312,
+ "y": 0.019300000742077827,
+ "z": 0.037427663803100586
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "a",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/keyCap_a.png",
+ "file10": "meshes/keyboard/keyCap_a.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.050909996032714844,
+ "y": 0.019300000742077827,
+ "z": -0.017487764358520508
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "e",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/keyCap_e.png",
+ "file10": "meshes/keyboard/keyCap_e.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.13773441314697266,
+ "y": 0.019300000742077827,
+ "z": 0.03745269775390625
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "s",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/keyCap_s.png",
+ "file10": "meshes/keyboard/keyCap_s.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.10659980773925781,
+ "y": 0.019300000742077827,
+ "z": -0.017481565475463867
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "u",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/keyCap_u.png",
+ "file10": "meshes/keyboard/keyCap_u.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.36423587799072266,
+ "y": 0.019300000742077827,
+ "z": 0.03743577003479004
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "h",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/keyCap_h.png",
+ "file10": "meshes/keyboard/keyCap_h.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.3321561813354492,
+ "y": 0.019300000742077827,
+ "z": -0.017461776733398438
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "j",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/keyCap_j.png",
+ "file10": "meshes/keyboard/keyCap_j.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.020519999787211418,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.38753604888916016,
+ "y": 0.019300000742077827,
+ "z": -0.01746058464050293
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "c",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/keyCap_c.png",
+ "file10": "meshes/keyboard/keyCap_c.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.22023773193359375,
+ "y": 0.019300000742077827,
+ "z": -0.07282757759094238
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "v",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/keyCap_v.png",
+ "file10": "meshes/keyboard/keyCap_v.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.2767200469970703,
+ "y": 0.019300000742077827,
+ "z": -0.07291850447654724
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "b",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/keyCap_b.png",
+ "file10": "meshes/keyboard/keyCap_b.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.020519999787211418,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.33254528045654297,
+ "y": 0.019300000742077827,
+ "z": -0.07283258438110352
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": " ",
+ "name": "space",
+ "dimensions": {
+ "x": 0.14597539603710175,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.27660465240478516,
+ "y": 0.019300000742077827,
+ "z": -0.12705934047698975
+ },
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/white.png",
+ "file10": "meshes/keyboard/white.png"
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "z",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/keyCap_z.png",
+ "file10": "meshes/keyboard/keyCap_z.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.10669422149658203,
+ "y": 0.019300000742077827,
+ "z": -0.07285571098327637
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "n",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/keyCap_n.png",
+ "file10": "meshes/keyboard/keyCap_n.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.020292000845074654,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.38794422149658203,
+ "y": 0.019300000742077827,
+ "z": -0.0728309154510498
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "m",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/keyCap_m.png",
+ "file10": "meshes/keyboard/keyCap_m.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.01846799999475479,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.44464683532714844,
+ "y": 0.019300000742077827,
+ "z": -0.07282185554504395
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "x",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/keyCap_x.png",
+ "file10": "meshes/keyboard/keyCap_x.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.1630725860595703,
+ "y": 0.019300000742077827,
+ "z": -0.07284045219421387
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "DEL",
+ "type": "backspace",
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.020519999787211418,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.53203323516845703,
+ "y": 0.019300000742077827,
+ "z": -0.07286686894893646
+ },
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/key_backspace.png",
+ "file10": "meshes/keyboard/key_backspace.png"
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "Caps",
+ "type": "caps",
+ "switchToLayer": 0,
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.020519999787211418,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.02603323516845703,
+ "y": 0.019300000742077827,
+ "z": -0.07285571098327637
+ },
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/key_cap.png",
+ "file10": "meshes/keyboard/key_cap.png"
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "Close",
+ "type": "close",
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.020519999787211418,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.59333323516845703,
+ "y": 0.019300000742077827,
+ "z": 0.037454843521118164
+ },
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/key_exit.png",
+ "file10": "meshes/keyboard/key_exit.png"
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "Enter",
+ "type": "enter",
+ "dimensions": {
+ "x": 0.08787999764084816,
+ "z": 0.020519999787211418,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.5103323516845703,
+ "y": 0.019300000742077827,
+ "z": -0.127054843521118164
+ },
+ "modelURL": "meshes/keyboard/SM_enter.fbx",
+ "texture": {
+ "file10": "meshes/keyboard/key_enter.png",
+ "file11": "meshes/keyboard/key_enter.png"
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "numbers",
+ "type": "layer",
+ "switchToLayer": 2,
+ "dimensions": {
+ "x": 0.07787999764084816,
+ "z": 0.020519999787211418,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.026323516845703,
+ "y": 0.019300000742077827,
+ "z": -0.127054843521118164
+ },
+ "modelURL": "meshes/keyboard/SM_enter.fbx",
+ "texture": {
+ "file10": "meshes/keyboard/key_123.png",
+ "file11": "meshes/keyboard/key_123.png"
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ }
+ ],
+ [
+ {
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.5332040786743164,
+ "y": 0.019300000742077827,
+ "z": 0.037454843521118164
+ },
+ "key": "0",
+ "texture": {
+ "file9": "meshes/keyboard/key_0.png",
+ "file10": "meshes/keyboard/key_0.png"
+ },
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.42067813873291016,
+ "y": 0.019300000742077827,
+ "z": 0.03744244575500488
+ },
+ "key": "8",
+ "texture": {
+ "file9": "meshes/keyboard/key_8.png",
+ "file10": "meshes/keyboard/key_8.png"
+ },
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "9",
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.47769832611083984,
+ "y": 0.019300000742077827,
+ "z": 0.03745675086975098
+ },
+ "texture": {
+ "file9": "meshes/keyboard/key_9.png",
+ "file10": "meshes/keyboard/key_9.png"
+ },
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.47764823303222656,
+ "y": 0.019300000742077827,
+ "z": -0.01745915412902832
+ },
+ "key": "(",
+ "texture": {
+ "file9": "meshes/keyboard/key_open_paren.png",
+ "file10": "meshes/keyboard/key_open_paren.png"
+ },
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.53364823303222656,
+ "y": 0.019300000742077827,
+ "z": -0.01745915412902832
+ },
+ "key": ")",
+ "texture": {
+ "file9": "meshes/keyboard/key_close_paren.png",
+ "file10": "meshes/keyboard/key_close_paren.png"
+ },
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.59634823303222656,
+ "y": 0.019300000742077827,
+ "z": -0.01745915412902832
+ },
+ "key": "/",
+ "texture": {
+ "file9": "meshes/keyboard/key_slash.png",
+ "file10": "meshes/keyboard/key_slash.png"
+ },
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.4206973831176758,
+ "y": 0.019300000742077827,
+ "z": -0.01745915412902832
+ },
+ "key": "+",
+ "texture": {
+ "file9": "meshes/keyboard/key_plus.png",
+ "file10": "meshes/keyboard/key_plus.png"
+ },
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "6",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/key_6.png",
+ "file10": "meshes/keyboard/key_6.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.30902957916259766,
+ "y": 0.019300000742077827,
+ "z": 0.0374448299407959
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "4",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/key_4.png",
+ "file10": "meshes/keyboard/key_4.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.020519999787211418,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.19474029541015625,
+ "y": 0.019300000742077827,
+ "z": 0.03745102882385254
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "$",
+ "texture": {
+ "file9": "meshes/keyboard/key_dollar.png",
+ "file10": "meshes/keyboard/key_dollar.png"
+ },
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.13772640228271484,
+ "y": 0.019300000742077827,
+ "z": -0.01747274398803711
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "5",
+ "texture": {
+ "file9": "meshes/keyboard/key_5.png",
+ "file10": "meshes/keyboard/key_5.png"
+ },
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.2517843246459961,
+ "y": 0.019300000742077827,
+ "z": 0.03744622692465782
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "%",
+ "texture": {
+ "file9": "meshes/keyboard/key_percentage.png",
+ "file10": "meshes/keyboard/key_percentage.png"
+ },
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.1947756072998047,
+ "y": 0.019300000742077827,
+ "z": -0.01746511459350586
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "_",
+ "texture": {
+ "file9": "meshes/keyboard/key_under.png",
+ "file10": "meshes/keyboard/key_under.png"
+ },
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.25172222900390625,
+ "y": 0.019300000742077827,
+ "z": -0.017457084730267525
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "2",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/key_2.png",
+ "file10": "meshes/keyboard/key_2.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.08203601837158203,
+ "y": 0.019300000742077827,
+ "z": 0.03743100166320801
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "1",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/key_1.png",
+ "file10": "meshes/keyboard/key_1.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.026292800903320312,
+ "y": 0.019300000742077827,
+ "z": 0.037427663803100586
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "@",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/key_at.png",
+ "file10": "meshes/keyboard/key_at.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.026209996032714844,
+ "y": 0.019300000742077827,
+ "z": -0.017487764358520508
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "3",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/key_3.png",
+ "file10": "meshes/keyboard/key_3.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.13773441314697266,
+ "y": 0.019300000742077827,
+ "z": 0.03745269775390625
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "#",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/key_hashtag.png",
+ "file10": "meshes/keyboard/key_hashtag.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.08209980773925781,
+ "y": 0.019300000742077827,
+ "z": -0.017481565475463867
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "7",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/key_7.png",
+ "file10": "meshes/keyboard/key_7.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.36423587799072266,
+ "y": 0.019300000742077827,
+ "z": 0.03743577003479004
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "&",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/key_ampersand.png",
+ "file10": "meshes/keyboard/key_ampersand.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.3090561813354492,
+ "y": 0.019300000742077827,
+ "z": -0.017461776733398438
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "-",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/key_min.png",
+ "file10": "meshes/keyboard/key_min.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.020519999787211418,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.36433604888916016,
+ "y": 0.019300000742077827,
+ "z": -0.01746058464050293
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "'",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/key_squote.png",
+ "file10": "meshes/keyboard/key_squote.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.22023773193359375,
+ "y": 0.019300000742077827,
+ "z": -0.07282757759094238
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": ":",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/key_colon.png",
+ "file10": "meshes/keyboard/key_colon.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.2767200469970703,
+ "y": 0.019300000742077827,
+ "z": -0.07291850447654724
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": ";",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/key_semi.png",
+ "file10": "meshes/keyboard/key_semi.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.020519999787211418,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.33254528045654297,
+ "y": 0.019300000742077827,
+ "z": -0.07283258438110352
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": " ",
+ "name": "space",
+ "dimensions": {
+ "x": 0.14597539603710175,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.27660465240478516,
+ "y": 0.019300000742077827,
+ "z": -0.12705934047698975
+ },
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/white.png",
+ "file10": "meshes/keyboard/white.png"
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "*",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/key_ast.png",
+ "file10": "meshes/keyboard/key_ast.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.10669422149658203,
+ "y": 0.019300000742077827,
+ "z": -0.07285571098327637
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "!",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/key_exclam.png",
+ "file10": "meshes/keyboard/key_exclam.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.020292000845074654,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.38794422149658203,
+ "y": 0.019300000742077827,
+ "z": -0.0728309154510498
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "?",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/key_question.png",
+ "file10": "meshes/keyboard/key_question.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.01846799999475479,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.44464683532714844,
+ "y": 0.019300000742077827,
+ "z": -0.07282185554504395
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "\"",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/key_dquote.png",
+ "file10": "meshes/keyboard/key_dquote.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.1630725860595703,
+ "y": 0.019300000742077827,
+ "z": -0.07284045219421387
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "DEL",
+ "type": "backspace",
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.020519999787211418,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.53203323516845703,
+ "y": 0.019300000742077827,
+ "z": -0.07286686894893646
+ },
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/key_backspace.png",
+ "file10": "meshes/keyboard/key_backspace.png"
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "Caps",
+ "type": "caps",
+ "switchToLayer": 1,
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.020519999787211418,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.02603323516845703,
+ "y": 0.019300000742077827,
+ "z": -0.07285571098327637
+ },
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/key_cap.png",
+ "file10": "meshes/keyboard/key_cap.png"
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "Close",
+ "type": "close",
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.020519999787211418,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.59333323516845703,
+ "y": 0.019300000742077827,
+ "z": 0.037454843521118164
+ },
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/key_exit.png",
+ "file10": "meshes/keyboard/key_exit.png"
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "Enter",
+ "type": "enter",
+ "dimensions": {
+ "x": 0.08787999764084816,
+ "z": 0.020519999787211418,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.5103323516845703,
+ "y": 0.019300000742077827,
+ "z": -0.127054843521118164
+ },
+ "modelURL": "meshes/keyboard/SM_enter.fbx",
+ "texture": {
+ "file10": "meshes/keyboard/key_enter.png",
+ "file11": "meshes/keyboard/key_enter.png"
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": "numbers",
+ "type": "layer",
+ "switchToLayer": 0,
+ "dimensions": {
+ "x": 0.07787999764084816,
+ "z": 0.020519999787211418,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.026323516845703,
+ "y": 0.019300000742077827,
+ "z": -0.127054843521118164
+ },
+ "modelURL": "meshes/keyboard/SM_enter.fbx",
+ "texture": {
+ "file10": "meshes/keyboard/key_abc.png",
+ "file11": "meshes/keyboard/key_abc.png"
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": ".",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/key_period.png",
+ "file10": "meshes/keyboard/key_period.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.38794422149658203,
+ "y": 0.019300000742077827,
+ "z": -0.12705934047698975
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ },
+ {
+ "key": ",",
+ "modelURL": "meshes/keyboard/SM_key.fbx",
+ "texture": {
+ "file9": "meshes/keyboard/key_comma.png",
+ "file10": "meshes/keyboard/key_comma.png"
+ },
+ "dimensions": {
+ "x": 0.04787999764084816,
+ "z": 0.02051999792456627,
+ "y": 0.04787999764084816
+ },
+ "position": {
+ "x": -0.1630725860595703,
+ "y": 0.019300000742077827,
+ "z": -0.12705934047698975
+ },
+ "localOrientation": {
+ "w": 0.000,
+ "x": 0.000,
+ "y": 0.707,
+ "z": 0.707
+ }
+ }
+ ]
+ ]
+}
diff --git a/interface/resources/meshes/drumstick.fbx b/interface/resources/meshes/drumstick.fbx
new file mode 100644
index 0000000000..0243d9fd1b
Binary files /dev/null and b/interface/resources/meshes/drumstick.fbx differ
diff --git a/interface/resources/meshes/keyboard/SM_enter.fbx b/interface/resources/meshes/keyboard/SM_enter.fbx
new file mode 100644
index 0000000000..119e3fc535
Binary files /dev/null and b/interface/resources/meshes/keyboard/SM_enter.fbx differ
diff --git a/interface/resources/meshes/keyboard/SM_key.fbx b/interface/resources/meshes/keyboard/SM_key.fbx
new file mode 100644
index 0000000000..02684a42d8
Binary files /dev/null and b/interface/resources/meshes/keyboard/SM_key.fbx differ
diff --git a/interface/resources/meshes/keyboard/SM_space.fbx b/interface/resources/meshes/keyboard/SM_space.fbx
new file mode 100644
index 0000000000..77632eb795
Binary files /dev/null and b/interface/resources/meshes/keyboard/SM_space.fbx differ
diff --git a/interface/resources/meshes/keyboard/keyCap_F.png b/interface/resources/meshes/keyboard/keyCap_F.png
new file mode 100644
index 0000000000..fba21a7d77
Binary files /dev/null and b/interface/resources/meshes/keyboard/keyCap_F.png differ
diff --git a/interface/resources/meshes/keyboard/keyCap_a.png b/interface/resources/meshes/keyboard/keyCap_a.png
new file mode 100644
index 0000000000..f409254292
Binary files /dev/null and b/interface/resources/meshes/keyboard/keyCap_a.png differ
diff --git a/interface/resources/meshes/keyboard/keyCap_b.png b/interface/resources/meshes/keyboard/keyCap_b.png
new file mode 100644
index 0000000000..5ab85290d1
Binary files /dev/null and b/interface/resources/meshes/keyboard/keyCap_b.png differ
diff --git a/interface/resources/meshes/keyboard/keyCap_c.png b/interface/resources/meshes/keyboard/keyCap_c.png
new file mode 100644
index 0000000000..9a020163ab
Binary files /dev/null and b/interface/resources/meshes/keyboard/keyCap_c.png differ
diff --git a/interface/resources/meshes/keyboard/keyCap_d.png b/interface/resources/meshes/keyboard/keyCap_d.png
new file mode 100644
index 0000000000..4eccee2dd5
Binary files /dev/null and b/interface/resources/meshes/keyboard/keyCap_d.png differ
diff --git a/interface/resources/meshes/keyboard/keyCap_e.png b/interface/resources/meshes/keyboard/keyCap_e.png
new file mode 100644
index 0000000000..09bd5bc289
Binary files /dev/null and b/interface/resources/meshes/keyboard/keyCap_e.png differ
diff --git a/interface/resources/meshes/keyboard/keyCap_g.png b/interface/resources/meshes/keyboard/keyCap_g.png
new file mode 100644
index 0000000000..9c52605428
Binary files /dev/null and b/interface/resources/meshes/keyboard/keyCap_g.png differ
diff --git a/interface/resources/meshes/keyboard/keyCap_h.png b/interface/resources/meshes/keyboard/keyCap_h.png
new file mode 100644
index 0000000000..f29323da11
Binary files /dev/null and b/interface/resources/meshes/keyboard/keyCap_h.png differ
diff --git a/interface/resources/meshes/keyboard/keyCap_i.png b/interface/resources/meshes/keyboard/keyCap_i.png
new file mode 100644
index 0000000000..721e6b84b1
Binary files /dev/null and b/interface/resources/meshes/keyboard/keyCap_i.png differ
diff --git a/interface/resources/meshes/keyboard/keyCap_j.png b/interface/resources/meshes/keyboard/keyCap_j.png
new file mode 100644
index 0000000000..7186df71c1
Binary files /dev/null and b/interface/resources/meshes/keyboard/keyCap_j.png differ
diff --git a/interface/resources/meshes/keyboard/keyCap_k.png b/interface/resources/meshes/keyboard/keyCap_k.png
new file mode 100644
index 0000000000..69667f42e0
Binary files /dev/null and b/interface/resources/meshes/keyboard/keyCap_k.png differ
diff --git a/interface/resources/meshes/keyboard/keyCap_l.png b/interface/resources/meshes/keyboard/keyCap_l.png
new file mode 100644
index 0000000000..d59c24f7e2
Binary files /dev/null and b/interface/resources/meshes/keyboard/keyCap_l.png differ
diff --git a/interface/resources/meshes/keyboard/keyCap_m.png b/interface/resources/meshes/keyboard/keyCap_m.png
new file mode 100644
index 0000000000..a6bc6729a8
Binary files /dev/null and b/interface/resources/meshes/keyboard/keyCap_m.png differ
diff --git a/interface/resources/meshes/keyboard/keyCap_n.png b/interface/resources/meshes/keyboard/keyCap_n.png
new file mode 100644
index 0000000000..950fb88e41
Binary files /dev/null and b/interface/resources/meshes/keyboard/keyCap_n.png differ
diff --git a/interface/resources/meshes/keyboard/keyCap_o.png b/interface/resources/meshes/keyboard/keyCap_o.png
new file mode 100644
index 0000000000..2c1755c887
Binary files /dev/null and b/interface/resources/meshes/keyboard/keyCap_o.png differ
diff --git a/interface/resources/meshes/keyboard/keyCap_p.png b/interface/resources/meshes/keyboard/keyCap_p.png
new file mode 100644
index 0000000000..366a9ef3ff
Binary files /dev/null and b/interface/resources/meshes/keyboard/keyCap_p.png differ
diff --git a/interface/resources/meshes/keyboard/keyCap_q.png b/interface/resources/meshes/keyboard/keyCap_q.png
new file mode 100644
index 0000000000..2d58bc2b46
Binary files /dev/null and b/interface/resources/meshes/keyboard/keyCap_q.png differ
diff --git a/interface/resources/meshes/keyboard/keyCap_r.png b/interface/resources/meshes/keyboard/keyCap_r.png
new file mode 100644
index 0000000000..053151aada
Binary files /dev/null and b/interface/resources/meshes/keyboard/keyCap_r.png differ
diff --git a/interface/resources/meshes/keyboard/keyCap_s.png b/interface/resources/meshes/keyboard/keyCap_s.png
new file mode 100644
index 0000000000..365a833af2
Binary files /dev/null and b/interface/resources/meshes/keyboard/keyCap_s.png differ
diff --git a/interface/resources/meshes/keyboard/keyCap_t.png b/interface/resources/meshes/keyboard/keyCap_t.png
new file mode 100644
index 0000000000..6ee42e3ae4
Binary files /dev/null and b/interface/resources/meshes/keyboard/keyCap_t.png differ
diff --git a/interface/resources/meshes/keyboard/keyCap_u.png b/interface/resources/meshes/keyboard/keyCap_u.png
new file mode 100644
index 0000000000..731467227a
Binary files /dev/null and b/interface/resources/meshes/keyboard/keyCap_u.png differ
diff --git a/interface/resources/meshes/keyboard/keyCap_v.png b/interface/resources/meshes/keyboard/keyCap_v.png
new file mode 100644
index 0000000000..1dbe395005
Binary files /dev/null and b/interface/resources/meshes/keyboard/keyCap_v.png differ
diff --git a/interface/resources/meshes/keyboard/keyCap_w.png b/interface/resources/meshes/keyboard/keyCap_w.png
new file mode 100644
index 0000000000..a71de5124d
Binary files /dev/null and b/interface/resources/meshes/keyboard/keyCap_w.png differ
diff --git a/interface/resources/meshes/keyboard/keyCap_x.png b/interface/resources/meshes/keyboard/keyCap_x.png
new file mode 100644
index 0000000000..232725dd6c
Binary files /dev/null and b/interface/resources/meshes/keyboard/keyCap_x.png differ
diff --git a/interface/resources/meshes/keyboard/keyCap_y.png b/interface/resources/meshes/keyboard/keyCap_y.png
new file mode 100644
index 0000000000..ed68e21384
Binary files /dev/null and b/interface/resources/meshes/keyboard/keyCap_y.png differ
diff --git a/interface/resources/meshes/keyboard/keyCap_z.png b/interface/resources/meshes/keyboard/keyCap_z.png
new file mode 100644
index 0000000000..a1fe8d4181
Binary files /dev/null and b/interface/resources/meshes/keyboard/keyCap_z.png differ
diff --git a/interface/resources/meshes/keyboard/key_0.png b/interface/resources/meshes/keyboard/key_0.png
new file mode 100644
index 0000000000..ff852cda9d
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_0.png differ
diff --git a/interface/resources/meshes/keyboard/key_1.png b/interface/resources/meshes/keyboard/key_1.png
new file mode 100644
index 0000000000..7115e92be8
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_1.png differ
diff --git a/interface/resources/meshes/keyboard/key_123.png b/interface/resources/meshes/keyboard/key_123.png
new file mode 100644
index 0000000000..07a3187a70
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_123.png differ
diff --git a/interface/resources/meshes/keyboard/key_2.png b/interface/resources/meshes/keyboard/key_2.png
new file mode 100644
index 0000000000..99a7e650f1
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_2.png differ
diff --git a/interface/resources/meshes/keyboard/key_3.png b/interface/resources/meshes/keyboard/key_3.png
new file mode 100644
index 0000000000..5ec80db3e0
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_3.png differ
diff --git a/interface/resources/meshes/keyboard/key_4.png b/interface/resources/meshes/keyboard/key_4.png
new file mode 100644
index 0000000000..e97261f2cd
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_4.png differ
diff --git a/interface/resources/meshes/keyboard/key_5.png b/interface/resources/meshes/keyboard/key_5.png
new file mode 100644
index 0000000000..59e059fbf4
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_5.png differ
diff --git a/interface/resources/meshes/keyboard/key_6.png b/interface/resources/meshes/keyboard/key_6.png
new file mode 100644
index 0000000000..bf4e81a7a1
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_6.png differ
diff --git a/interface/resources/meshes/keyboard/key_7.png b/interface/resources/meshes/keyboard/key_7.png
new file mode 100644
index 0000000000..5d9765b37e
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_7.png differ
diff --git a/interface/resources/meshes/keyboard/key_8.png b/interface/resources/meshes/keyboard/key_8.png
new file mode 100644
index 0000000000..f905e2220c
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_8.png differ
diff --git a/interface/resources/meshes/keyboard/key_9.png b/interface/resources/meshes/keyboard/key_9.png
new file mode 100644
index 0000000000..89a6397c82
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_9.png differ
diff --git a/interface/resources/meshes/keyboard/key_a.png b/interface/resources/meshes/keyboard/key_a.png
new file mode 100644
index 0000000000..74d57d5bd4
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_a.png differ
diff --git a/interface/resources/meshes/keyboard/key_abc.png b/interface/resources/meshes/keyboard/key_abc.png
new file mode 100644
index 0000000000..5b7f1bcb0f
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_abc.png differ
diff --git a/interface/resources/meshes/keyboard/key_ampersand.png b/interface/resources/meshes/keyboard/key_ampersand.png
new file mode 100644
index 0000000000..e8e06892f8
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_ampersand.png differ
diff --git a/interface/resources/meshes/keyboard/key_ast.png b/interface/resources/meshes/keyboard/key_ast.png
new file mode 100644
index 0000000000..1c6f03ed4a
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_ast.png differ
diff --git a/interface/resources/meshes/keyboard/key_at.png b/interface/resources/meshes/keyboard/key_at.png
new file mode 100644
index 0000000000..0d0e9019a8
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_at.png differ
diff --git a/interface/resources/meshes/keyboard/key_b.png b/interface/resources/meshes/keyboard/key_b.png
new file mode 100644
index 0000000000..50b607cbd3
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_b.png differ
diff --git a/interface/resources/meshes/keyboard/key_backspace.png b/interface/resources/meshes/keyboard/key_backspace.png
new file mode 100644
index 0000000000..db56841a4a
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_backspace.png differ
diff --git a/interface/resources/meshes/keyboard/key_c.png b/interface/resources/meshes/keyboard/key_c.png
new file mode 100644
index 0000000000..da8a4a4f2d
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_c.png differ
diff --git a/interface/resources/meshes/keyboard/key_cap.png b/interface/resources/meshes/keyboard/key_cap.png
new file mode 100644
index 0000000000..1e3c4c1b9f
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_cap.png differ
diff --git a/interface/resources/meshes/keyboard/key_caret.png b/interface/resources/meshes/keyboard/key_caret.png
new file mode 100644
index 0000000000..05011b905a
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_caret.png differ
diff --git a/interface/resources/meshes/keyboard/key_close_paren.png b/interface/resources/meshes/keyboard/key_close_paren.png
new file mode 100644
index 0000000000..6a93b0fe05
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_close_paren.png differ
diff --git a/interface/resources/meshes/keyboard/key_colon.png b/interface/resources/meshes/keyboard/key_colon.png
new file mode 100644
index 0000000000..a4f025349c
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_colon.png differ
diff --git a/interface/resources/meshes/keyboard/key_comma.png b/interface/resources/meshes/keyboard/key_comma.png
new file mode 100644
index 0000000000..69da507ec6
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_comma.png differ
diff --git a/interface/resources/meshes/keyboard/key_d.png b/interface/resources/meshes/keyboard/key_d.png
new file mode 100644
index 0000000000..557f3816f0
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_d.png differ
diff --git a/interface/resources/meshes/keyboard/key_dollar.png b/interface/resources/meshes/keyboard/key_dollar.png
new file mode 100644
index 0000000000..0105debb68
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_dollar.png differ
diff --git a/interface/resources/meshes/keyboard/key_dquote.png b/interface/resources/meshes/keyboard/key_dquote.png
new file mode 100644
index 0000000000..393ab9b748
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_dquote.png differ
diff --git a/interface/resources/meshes/keyboard/key_e.png b/interface/resources/meshes/keyboard/key_e.png
new file mode 100644
index 0000000000..1b356d9d5b
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_e.png differ
diff --git a/interface/resources/meshes/keyboard/key_enter.png b/interface/resources/meshes/keyboard/key_enter.png
new file mode 100644
index 0000000000..cf935fe07e
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_enter.png differ
diff --git a/interface/resources/meshes/keyboard/key_exclam.png b/interface/resources/meshes/keyboard/key_exclam.png
new file mode 100644
index 0000000000..e9f68ced1a
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_exclam.png differ
diff --git a/interface/resources/meshes/keyboard/key_exit.png b/interface/resources/meshes/keyboard/key_exit.png
new file mode 100644
index 0000000000..4c06660d56
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_exit.png differ
diff --git a/interface/resources/meshes/keyboard/key_f.png b/interface/resources/meshes/keyboard/key_f.png
new file mode 100644
index 0000000000..93306c6035
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_f.png differ
diff --git a/interface/resources/meshes/keyboard/key_g.png b/interface/resources/meshes/keyboard/key_g.png
new file mode 100644
index 0000000000..9fda692c04
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_g.png differ
diff --git a/interface/resources/meshes/keyboard/key_h.png b/interface/resources/meshes/keyboard/key_h.png
new file mode 100644
index 0000000000..c73f37c271
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_h.png differ
diff --git a/interface/resources/meshes/keyboard/key_hashtag.png b/interface/resources/meshes/keyboard/key_hashtag.png
new file mode 100644
index 0000000000..df673653b0
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_hashtag.png differ
diff --git a/interface/resources/meshes/keyboard/key_i.png b/interface/resources/meshes/keyboard/key_i.png
new file mode 100644
index 0000000000..6277d8085c
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_i.png differ
diff --git a/interface/resources/meshes/keyboard/key_j.png b/interface/resources/meshes/keyboard/key_j.png
new file mode 100644
index 0000000000..f723de1f55
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_j.png differ
diff --git a/interface/resources/meshes/keyboard/key_k.png b/interface/resources/meshes/keyboard/key_k.png
new file mode 100644
index 0000000000..aa3e806a8d
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_k.png differ
diff --git a/interface/resources/meshes/keyboard/key_l.png b/interface/resources/meshes/keyboard/key_l.png
new file mode 100644
index 0000000000..4b31a84f32
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_l.png differ
diff --git a/interface/resources/meshes/keyboard/key_m.png b/interface/resources/meshes/keyboard/key_m.png
new file mode 100644
index 0000000000..2e83b7b214
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_m.png differ
diff --git a/interface/resources/meshes/keyboard/key_min.png b/interface/resources/meshes/keyboard/key_min.png
new file mode 100644
index 0000000000..e45beb4e7c
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_min.png differ
diff --git a/interface/resources/meshes/keyboard/key_n.png b/interface/resources/meshes/keyboard/key_n.png
new file mode 100644
index 0000000000..be1a6a9e7a
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_n.png differ
diff --git a/interface/resources/meshes/keyboard/key_o.png b/interface/resources/meshes/keyboard/key_o.png
new file mode 100644
index 0000000000..883feec871
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_o.png differ
diff --git a/interface/resources/meshes/keyboard/key_open_paren.png b/interface/resources/meshes/keyboard/key_open_paren.png
new file mode 100644
index 0000000000..9dcee0b9a0
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_open_paren.png differ
diff --git a/interface/resources/meshes/keyboard/key_p.png b/interface/resources/meshes/keyboard/key_p.png
new file mode 100644
index 0000000000..8d55d351ce
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_p.png differ
diff --git a/interface/resources/meshes/keyboard/key_percentage.png b/interface/resources/meshes/keyboard/key_percentage.png
new file mode 100644
index 0000000000..fbe1fa9599
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_percentage.png differ
diff --git a/interface/resources/meshes/keyboard/key_period.png b/interface/resources/meshes/keyboard/key_period.png
new file mode 100644
index 0000000000..ff726df39b
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_period.png differ
diff --git a/interface/resources/meshes/keyboard/key_plus.png b/interface/resources/meshes/keyboard/key_plus.png
new file mode 100644
index 0000000000..3eab84c34d
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_plus.png differ
diff --git a/interface/resources/meshes/keyboard/key_q.png b/interface/resources/meshes/keyboard/key_q.png
new file mode 100644
index 0000000000..fc733fd7fd
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_q.png differ
diff --git a/interface/resources/meshes/keyboard/key_question.png b/interface/resources/meshes/keyboard/key_question.png
new file mode 100644
index 0000000000..57f5187213
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_question.png differ
diff --git a/interface/resources/meshes/keyboard/key_r.png b/interface/resources/meshes/keyboard/key_r.png
new file mode 100644
index 0000000000..6277c64097
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_r.png differ
diff --git a/interface/resources/meshes/keyboard/key_s.png b/interface/resources/meshes/keyboard/key_s.png
new file mode 100644
index 0000000000..1fc1085391
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_s.png differ
diff --git a/interface/resources/meshes/keyboard/key_semi.png b/interface/resources/meshes/keyboard/key_semi.png
new file mode 100644
index 0000000000..5cb1b495a4
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_semi.png differ
diff --git a/interface/resources/meshes/keyboard/key_slash.png b/interface/resources/meshes/keyboard/key_slash.png
new file mode 100644
index 0000000000..be7b0fecb4
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_slash.png differ
diff --git a/interface/resources/meshes/keyboard/key_squote.png b/interface/resources/meshes/keyboard/key_squote.png
new file mode 100644
index 0000000000..3239c19ceb
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_squote.png differ
diff --git a/interface/resources/meshes/keyboard/key_t.png b/interface/resources/meshes/keyboard/key_t.png
new file mode 100644
index 0000000000..c2082b8f51
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_t.png differ
diff --git a/interface/resources/meshes/keyboard/key_u.png b/interface/resources/meshes/keyboard/key_u.png
new file mode 100644
index 0000000000..657527f6c0
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_u.png differ
diff --git a/interface/resources/meshes/keyboard/key_under.png b/interface/resources/meshes/keyboard/key_under.png
new file mode 100644
index 0000000000..3694dd3109
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_under.png differ
diff --git a/interface/resources/meshes/keyboard/key_v.png b/interface/resources/meshes/keyboard/key_v.png
new file mode 100644
index 0000000000..c061ca6fa0
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_v.png differ
diff --git a/interface/resources/meshes/keyboard/key_w.png b/interface/resources/meshes/keyboard/key_w.png
new file mode 100644
index 0000000000..15de9b25a8
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_w.png differ
diff --git a/interface/resources/meshes/keyboard/key_x.png b/interface/resources/meshes/keyboard/key_x.png
new file mode 100644
index 0000000000..d81a423f3b
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_x.png differ
diff --git a/interface/resources/meshes/keyboard/key_y.png b/interface/resources/meshes/keyboard/key_y.png
new file mode 100644
index 0000000000..cb85af5b32
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_y.png differ
diff --git a/interface/resources/meshes/keyboard/key_z.png b/interface/resources/meshes/keyboard/key_z.png
new file mode 100644
index 0000000000..462531351d
Binary files /dev/null and b/interface/resources/meshes/keyboard/key_z.png differ
diff --git a/interface/resources/meshes/keyboard/text_placard.png b/interface/resources/meshes/keyboard/text_placard.png
new file mode 100644
index 0000000000..b0a5953a30
Binary files /dev/null and b/interface/resources/meshes/keyboard/text_placard.png differ
diff --git a/interface/resources/meshes/keyboard/white.png b/interface/resources/meshes/keyboard/white.png
new file mode 100644
index 0000000000..9673f508fc
Binary files /dev/null and b/interface/resources/meshes/keyboard/white.png differ
diff --git a/interface/resources/qml/controls/TabletWebView.qml b/interface/resources/qml/controls/TabletWebView.qml
index 0c5ca37e00..94f4c7978c 100644
--- a/interface/resources/qml/controls/TabletWebView.qml
+++ b/interface/resources/qml/controls/TabletWebView.qml
@@ -195,6 +195,10 @@ Item {
keyboardEnabled = HMD.active;
}
+ Component.onDestruction: {
+ keyboardRaised = false;
+ }
+
Keys.onPressed: {
switch(event.key) {
case Qt.Key_L:
diff --git a/interface/resources/qml/controlsUit/Keyboard.qml b/interface/resources/qml/controlsUit/Keyboard.qml
index 9d4fd33022..c38631ff79 100644
--- a/interface/resources/qml/controlsUit/Keyboard.qml
+++ b/interface/resources/qml/controlsUit/Keyboard.qml
@@ -36,13 +36,29 @@ Rectangle {
readonly property int raisedHeight: keyboardHeight + (showMirrorText ? keyboardRowHeight : 0)
- height: enabled && raised ? raisedHeight : 0
- visible: enabled && raised
+ height: 0
+ visible: false
property bool shiftMode: false
property bool numericShiftMode: false
+
+ onPasswordChanged: {
+ var use3DKeyboard = (typeof MenuInterface === "undefined") ? false : MenuInterface.isOptionChecked("Use 3D Keyboard");
+ if (use3DKeyboard) {
+ KeyboardScriptingInterface.password = password;
+ }
+ }
+
onRaisedChanged: {
+ var use3DKeyboard = (typeof MenuInterface === "undefined") ? false : MenuInterface.isOptionChecked("Use 3D Keyboard");
+ if (!use3DKeyboard) {
+ keyboardBase.height = raised ? raisedHeight : 0;
+ keyboardBase.visible = raised;
+ } else {
+ KeyboardScriptingInterface.raised = raised;
+ KeyboardScriptingInterface.password = raised ? password : false;
+ }
mirroredText = "";
}
diff --git a/interface/resources/qml/dialogs/TabletLoginDialog.qml b/interface/resources/qml/dialogs/TabletLoginDialog.qml
index 6314921286..dad2bb91aa 100644
--- a/interface/resources/qml/dialogs/TabletLoginDialog.qml
+++ b/interface/resources/qml/dialogs/TabletLoginDialog.qml
@@ -95,6 +95,18 @@ TabletModalWindow {
}
}
+ Timer {
+ id: keyboardTimer
+ repeat: false
+ interval: 200
+
+ onTriggered: {
+ if (MenuInterface.isOptionChecked("Use 3D Keyboard")) {
+ KeyboardScriptingInterface.raised = true;
+ }
+ }
+ }
+
TabletModalFrame {
id: mfRoot
@@ -127,6 +139,14 @@ TabletModalWindow {
}
}
+ Component.onDestruction: {
+ loginKeyboard.raised = false;
+ }
+
+ Component.onCompleted: {
+ keyboardTimer.start();
+ }
+
Keyboard {
id: loginKeyboard
raised: root.keyboardEnabled && root.keyboardRaised
diff --git a/interface/resources/qml/hifi/AvatarApp.qml b/interface/resources/qml/hifi/AvatarApp.qml
index 39590748cf..9635681c34 100644
--- a/interface/resources/qml/hifi/AvatarApp.qml
+++ b/interface/resources/qml/hifi/AvatarApp.qml
@@ -19,7 +19,7 @@ Rectangle {
HifiControls.Keyboard {
id: keyboard
z: 1000
- raised: parent.keyboardEnabled && parent.keyboardRaised
+ raised: parent.keyboardEnabled && parent.keyboardRaised && HMD.active
numeric: parent.punctuationMode
anchors {
left: parent.left
@@ -204,7 +204,8 @@ Rectangle {
property bool isInManageState: false
- Component.onCompleted: {
+ Component.onDestruction: {
+ keyboard.raised = false;
}
AvatarAppStyle {
@@ -235,6 +236,8 @@ Rectangle {
avatarIconVisible: mainPageVisible
settingsButtonVisible: mainPageVisible
onSettingsClicked: {
+ displayNameInput.focus = false;
+ root.keyboardRaised = false;
settings.open(currentAvatarSettings, currentAvatar.avatarScale);
}
}
@@ -344,6 +347,10 @@ Rectangle {
emitSendToScript({'method' : 'changeDisplayName', 'displayName' : text})
focus = false;
}
+
+ onFocusChanged: {
+ root.keyboardRaised = focus;
+ }
}
ShadowImage {
diff --git a/interface/resources/qml/hifi/avatarapp/Settings.qml b/interface/resources/qml/hifi/avatarapp/Settings.qml
index bad1394133..cd892c17b1 100644
--- a/interface/resources/qml/hifi/avatarapp/Settings.qml
+++ b/interface/resources/qml/hifi/avatarapp/Settings.qml
@@ -14,6 +14,22 @@ Rectangle {
signal scaleChanged(real scale);
+ property bool keyboardEnabled: true
+ property bool keyboardRaised: false
+ property bool punctuationMode: false
+
+ HifiControlsUit.Keyboard {
+ id: keyboard
+ z: 1000
+ raised: parent.keyboardEnabled && parent.keyboardRaised
+ numeric: parent.punctuationMode
+ anchors {
+ left: parent.left
+ right: parent.right
+ bottom: parent.bottom
+ }
+ }
+
property alias onSaveClicked: dialogButtons.onYesClicked
property alias onCancelClicked: dialogButtons.onNoClicked
@@ -314,6 +330,10 @@ Rectangle {
anchors.left: parent.left
anchors.right: parent.right
placeholderText: 'user\\file\\dir'
+
+ onFocusChanged: {
+ keyboardRaised = (avatarAnimationUrlInputText.focus || avatarCollisionSoundUrlInputText.focus);
+ }
}
}
@@ -340,6 +360,10 @@ Rectangle {
anchors.left: parent.left
anchors.right: parent.right
placeholderText: 'https://hifi-public.s3.amazonaws.com/sounds/Collisions-'
+
+ onFocusChanged: {
+ keyboardRaised = (avatarAnimationUrlInputText.focus || avatarCollisionSoundUrlInputText.focus);
+ }
}
}
diff --git a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml
index ed4ba66b2b..81fec4ace3 100644
--- a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml
+++ b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml
@@ -40,6 +40,10 @@ Rectangle {
source: "images/wallet-bg.jpg";
}
+ Component.onDestruction: {
+ KeyboardScriptingInterface.raised = false;
+ }
+
Connections {
target: Commerce;
diff --git a/interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml b/interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml
index 6cd220307d..b0f17ff841 100644
--- a/interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml
+++ b/interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml
@@ -88,6 +88,10 @@ Rectangle {
checkMenu.restart();
}
+ Component.onDestruction: {
+ keyboard.raised = false;
+ }
+
function updateRunningScripts() {
function simplify(path) {
// trim URI querystring/fragment
diff --git a/interface/resources/qml/hifi/tablet/Edit.qml b/interface/resources/qml/hifi/tablet/Edit.qml
index 4acced86ce..099c53cda2 100644
--- a/interface/resources/qml/hifi/tablet/Edit.qml
+++ b/interface/resources/qml/hifi/tablet/Edit.qml
@@ -49,5 +49,11 @@ StackView {
if (currentItem && currentItem.fromScript)
currentItem.fromScript(message);
}
+
+ Component.onDestruction: {
+ if (KeyboardScriptingInterface.raised) {
+ KeyboardScriptingInterface.raised = false;
+ }
+ }
}
diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml
index 0f26ba20aa..b8972378ad 100644
--- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml
+++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml
@@ -56,10 +56,13 @@ StackView {
Qt.callLater(function() {
addressBarDialog.keyboardEnabled = HMD.active;
addressLine.forceActiveFocus();
+ addressBarDialog.raised = true;
})
}
+
Component.onDestruction: {
root.parentChanged.disconnect(center);
+ keyboard.raised = false;
}
function center() {
@@ -218,6 +221,11 @@ StackView {
leftMargin: 8;
verticalCenter: addressLineContainer.verticalCenter;
}
+
+ onFocusChanged: {
+ addressBarDialog.raised = focus;
+ }
+
onTextChanged: {
updateLocationText(text.length > 0);
}
diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml b/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml
index 57ca705352..a5d7b23df6 100644
--- a/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml
+++ b/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml
@@ -242,6 +242,10 @@ Item {
keyboardEnabled = HMD.active;
}
+ Component.onDestruction: {
+ keyboard.raised = false;
+ }
+
onKeyboardRaisedChanged: {
if (keyboardEnabled && keyboardRaised) {
var delta = mouseArea.mouseY - (dialog.height - footer.height - keyboard.raisedHeight -hifi.dimensions.controlLineHeight);
diff --git a/interface/resources/qml/hifi/tts/TTS.qml b/interface/resources/qml/hifi/tts/TTS.qml
new file mode 100644
index 0000000000..d9507f6084
--- /dev/null
+++ b/interface/resources/qml/hifi/tts/TTS.qml
@@ -0,0 +1,314 @@
+//
+// TTS.qml
+//
+// TTS App
+//
+// Created by Zach Fox on 2018-10-10
+// 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
+//
+
+import Hifi 1.0 as Hifi
+import QtQuick 2.10
+import QtQuick.Controls 2.3
+import "qrc:////qml//styles-uit" as HifiStylesUit
+import "qrc:////qml//controls-uit" as HifiControlsUit
+import "qrc:////qml//controls" as HifiControls
+
+Rectangle {
+ HifiStylesUit.HifiConstants { id: hifi; }
+
+ id: root;
+ // Style
+ color: hifi.colors.darkGray;
+ property bool keyboardRaised: false;
+
+ //
+ // TITLE BAR START
+ //
+ Item {
+ id: titleBarContainer;
+ // Size
+ width: root.width;
+ height: 50;
+ // Anchors
+ anchors.left: parent.left;
+ anchors.top: parent.top;
+
+ // Title bar text
+ HifiStylesUit.RalewaySemiBold {
+ id: titleBarText;
+ text: "Text-to-Speech";
+ // Text size
+ size: hifi.fontSizes.overlayTitle;
+ // Anchors
+ anchors.top: parent.top;
+ anchors.bottom: parent.bottom;
+ anchors.left: parent.left;
+ anchors.leftMargin: 16;
+ width: paintedWidth;
+ // Style
+ color: hifi.colors.lightGrayText;
+ // Alignment
+ horizontalAlignment: Text.AlignHLeft;
+ verticalAlignment: Text.AlignVCenter;
+ }
+
+ // Separator
+ HifiControlsUit.Separator {
+ anchors.left: parent.left;
+ anchors.right: parent.right;
+ anchors.bottom: parent.bottom;
+ }
+ }
+ //
+ // TITLE BAR END
+ //
+
+
+ Item {
+ id: tagButtonContainer;
+ anchors.top: titleBarContainer.bottom;
+ anchors.topMargin: 2;
+ anchors.left: parent.left;
+ anchors.right: parent.right;
+ height: 70;
+
+ HifiStylesUit.RalewaySemiBold {
+ id: tagButtonTitle;
+ text: "Insert Tag:";
+ // Text size
+ size: 18;
+ // Anchors
+ anchors.top: parent.top;
+ anchors.left: parent.left;
+ anchors.right: parent.right;
+ height: 35;
+ // Style
+ color: hifi.colors.lightGrayText;
+ // Alignment
+ horizontalAlignment: Text.AlignHCenter;
+ verticalAlignment: Text.AlignVCenter;
+ }
+
+ HifiControlsUit.Button {
+ id: pitch10Button;
+ focusPolicy: Qt.NoFocus;
+ color: hifi.buttons.none;
+ colorScheme: hifi.colorSchemes.dark;
+ anchors.top: tagButtonTitle.bottom;
+ anchors.left: parent.left;
+ anchors.leftMargin: 3;
+ width: parent.width/6 - 6;
+ height: 30;
+ text: "Pitch 10";
+ onClicked: {
+ messageToSpeak.insert(messageToSpeak.cursorPosition, "");
+ }
+ }
+
+ HifiControlsUit.Button {
+ id: pitch0Button;
+ focusPolicy: Qt.NoFocus;
+ color: hifi.buttons.none;
+ colorScheme: hifi.colorSchemes.dark;
+ anchors.top: tagButtonTitle.bottom;
+ anchors.left: pitch10Button.right;
+ anchors.leftMargin: 6;
+ width: parent.width/6 - anchors.leftMargin;
+ height: 30;
+ text: "Pitch 0";
+ onClicked: {
+ messageToSpeak.insert(messageToSpeak.cursorPosition, "");
+ }
+ }
+
+ HifiControlsUit.Button {
+ id: pitchNeg10Button;
+ focusPolicy: Qt.NoFocus;
+ color: hifi.buttons.none;
+ colorScheme: hifi.colorSchemes.dark;
+ anchors.top: tagButtonTitle.bottom;
+ anchors.left: pitch0Button.right;
+ anchors.leftMargin: 6;
+ width: parent.width/6 - anchors.leftMargin;
+ height: 30;
+ text: "Pitch -10";
+ onClicked: {
+ messageToSpeak.insert(messageToSpeak.cursorPosition, "");
+ }
+ }
+
+ HifiControlsUit.Button {
+ id: speed5Button;
+ focusPolicy: Qt.NoFocus;
+ color: hifi.buttons.none;
+ colorScheme: hifi.colorSchemes.dark;
+ anchors.top: tagButtonTitle.bottom;
+ anchors.left: pitchNeg10Button.right;
+ anchors.leftMargin: 6;
+ width: parent.width/6 - anchors.leftMargin;
+ height: 30;
+ text: "Speed 5";
+ onClicked: {
+ messageToSpeak.insert(messageToSpeak.cursorPosition, "");
+ }
+ }
+
+ HifiControlsUit.Button {
+ id: speed0Button;
+ focusPolicy: Qt.NoFocus;
+ color: hifi.buttons.none;
+ colorScheme: hifi.colorSchemes.dark;
+ anchors.top: tagButtonTitle.bottom;
+ anchors.left: speed5Button.right;
+ anchors.leftMargin: 6;
+ width: parent.width/6 - anchors.leftMargin;
+ height: 30;
+ text: "Speed 0";
+ onClicked: {
+ messageToSpeak.insert(messageToSpeak.cursorPosition, "");
+ }
+ }
+
+ HifiControlsUit.Button {
+ id: speedNeg10Button;
+ focusPolicy: Qt.NoFocus;
+ color: hifi.buttons.none;
+ colorScheme: hifi.colorSchemes.dark;
+ anchors.top: tagButtonTitle.bottom;
+ anchors.left: speed0Button.right;
+ anchors.leftMargin: 6;
+ width: parent.width/6 - anchors.leftMargin;
+ height: 30;
+ text: "Speed -10";
+ onClicked: {
+ messageToSpeak.insert(messageToSpeak.cursorPosition, "");
+ }
+ }
+ }
+
+ Item {
+ anchors.top: tagButtonContainer.bottom;
+ anchors.topMargin: 8;
+ anchors.bottom: keyboardContainer.top;
+ anchors.bottomMargin: 16;
+ anchors.left: parent.left;
+ anchors.leftMargin: 16;
+ anchors.right: parent.right;
+ anchors.rightMargin: 16;
+
+ TextArea {
+ id: messageToSpeak;
+ font.family: "Fira Sans SemiBold";
+ font.pixelSize: 20;
+ // Anchors
+ anchors.top: parent.top;
+ anchors.left: parent.left;
+ anchors.right: parent.right;
+ anchors.bottom: speakButton.top;
+ anchors.bottomMargin: 8;
+ // Style
+ background: Rectangle {
+ anchors.fill: parent;
+ color: parent.activeFocus ? hifi.colors.black : hifi.colors.baseGrayShadow;
+ border.width: parent.activeFocus ? 1 : 0;
+ border.color: parent.activeFocus ? hifi.colors.primaryHighlight : hifi.colors.textFieldLightBackground;
+ }
+ color: hifi.colors.white;
+ textFormat: TextEdit.PlainText;
+ wrapMode: TextEdit.Wrap;
+ activeFocusOnPress: true;
+ activeFocusOnTab: true;
+ Keys.onPressed: {
+ if (event.key == Qt.Key_Return || event.key == Qt.Key_Enter) {
+ TextToSpeech.speakText(messageToSpeak.text, 480, 10, 24000, 16, true);
+ event.accepted = true;
+ }
+ }
+
+ HifiStylesUit.FiraSansRegular {
+ text: "Input Text to Speak...";
+ size: 20;
+ anchors.fill: parent;
+ anchors.topMargin: 4;
+ anchors.leftMargin: 4;
+ color: hifi.colors.lightGrayText;
+ visible: !parent.activeFocus && messageToSpeak.text === "";
+ verticalAlignment: Text.AlignTop;
+ }
+ }
+
+ HifiControlsUit.Button {
+ id: speakButton;
+ focusPolicy: Qt.NoFocus;
+ color: hifi.buttons.blue;
+ colorScheme: hifi.colorSchemes.dark;
+ anchors.right: parent.right;
+ anchors.bottom: parent.bottom;
+ width: 215;
+ height: 40;
+ text: "Speak";
+ onClicked: {
+ TextToSpeech.speakText(messageToSpeak.text, 480, 10, 24000, 16, true);
+ }
+ }
+
+ HifiControlsUit.Button {
+ id: clearButton;
+ focusPolicy: Qt.NoFocus;
+ color: hifi.buttons.white;
+ colorScheme: hifi.colorSchemes.dark;
+ anchors.right: speakButton.left;
+ anchors.rightMargin: 16;
+ anchors.bottom: parent.bottom;
+ width: 100;
+ height: 40;
+ text: "Clear";
+ onClicked: {
+ messageToSpeak.text = "";
+ }
+ }
+
+ HifiControlsUit.Button {
+ id: stopButton;
+ focusPolicy: Qt.NoFocus;
+ color: hifi.buttons.red;
+ colorScheme: hifi.colorSchemes.dark;
+ anchors.right: clearButton.left;
+ anchors.rightMargin: 16;
+ anchors.bottom: parent.bottom;
+ width: 100;
+ height: 40;
+ text: "Stop Last";
+ onClicked: {
+ TextToSpeech.stopLastSpeech();
+ }
+ }
+ }
+
+ Item {
+ id: keyboardContainer;
+ z: 998;
+ visible: keyboard.raised;
+ property bool punctuationMode: false;
+ anchors {
+ bottom: parent.bottom;
+ left: parent.left;
+ right: parent.right;
+ }
+
+ HifiControlsUit.Keyboard {
+ id: keyboard;
+ raised: HMD.mounted && root.keyboardRaised;
+ numeric: parent.punctuationMode;
+ anchors {
+ bottom: parent.bottom;
+ left: parent.left;
+ right: parent.right;
+ }
+ }
+ }
+}
diff --git a/interface/resources/sounds/keyboard_key.mp3 b/interface/resources/sounds/keyboard_key.mp3
new file mode 100644
index 0000000000..e2cec81032
Binary files /dev/null and b/interface/resources/sounds/keyboard_key.mp3 differ
diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index c2faf8494a..368f4d4d9a 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -186,6 +186,8 @@
#include "scripting/RatesScriptingInterface.h"
#include "scripting/SelectionScriptingInterface.h"
#include "scripting/WalletScriptingInterface.h"
+#include "scripting/TTSScriptingInterface.h"
+#include "scripting/KeyboardScriptingInterface.h"
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
#include "SpeechRecognizer.h"
#endif
@@ -204,6 +206,7 @@
#include "ui/UpdateDialog.h"
#include "ui/overlays/Overlays.h"
#include "ui/DomainConnectionModel.h"
+#include "ui/Keyboard.h"
#include "Util.h"
#include "InterfaceParentFinder.h"
#include "ui/OctreeStatsProvider.h"
@@ -533,11 +536,11 @@ bool isDomainURL(QUrl url) {
if (url.scheme() == URL_SCHEME_HIFI) {
return true;
}
- if (url.scheme() != URL_SCHEME_FILE) {
+ if (url.scheme() != HIFI_URL_SCHEME_FILE) {
// TODO -- once Octree::readFromURL no-longer takes over the main event-loop, serverless-domain urls can
// be loaded over http(s)
- // && url.scheme() != URL_SCHEME_HTTP &&
- // url.scheme() != URL_SCHEME_HTTPS
+ // && url.scheme() != HIFI_URL_SCHEME_HTTP &&
+ // url.scheme() != HIFI_URL_SCHEME_HTTPS
return false;
}
if (url.path().endsWith(".json", Qt::CaseInsensitive) ||
@@ -948,9 +951,12 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
DependencyManager::set();
DependencyManager::set();
DependencyManager::set();
+ DependencyManager::set();
DependencyManager::set();
DependencyManager::set();
+ DependencyManager::set();
+ DependencyManager::set();
return previousSessionCrashed;
}
@@ -1032,8 +1038,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
// If the URL scheme is http(s) or ftp, then use as is, else - treat it as a local file
// This is done so as not break previous command line scripts
- if (testScriptPath.left(URL_SCHEME_HTTP.length()) == URL_SCHEME_HTTP ||
- testScriptPath.left(URL_SCHEME_FTP.length()) == URL_SCHEME_FTP) {
+ if (testScriptPath.left(HIFI_URL_SCHEME_HTTP.length()) == HIFI_URL_SCHEME_HTTP ||
+ testScriptPath.left(HIFI_URL_SCHEME_FTP.length()) == HIFI_URL_SCHEME_FTP) {
setProperty(hifi::properties::TEST, QUrl::fromUserInput(testScriptPath));
} else if (QFileInfo(testScriptPath).exists()) {
@@ -2325,6 +2331,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
// Preload Tablet sounds
DependencyManager::get()->preloadSounds();
+ DependencyManager::get()->createKeyboard();
_pendingIdleEvent = false;
_pendingRenderEvent = false;
@@ -2337,23 +2344,29 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
connect(&AndroidHelper::instance(), &AndroidHelper::enterForeground, this, &Application::enterForeground);
AndroidHelper::instance().notifyLoadComplete();
#else
- static int CHECK_LOGIN_TIMER = 3000;
- QTimer* checkLoginTimer = new QTimer(this);
- checkLoginTimer->setInterval(CHECK_LOGIN_TIMER);
- checkLoginTimer->setSingleShot(true);
- connect(checkLoginTimer, &QTimer::timeout, this, []() {
- auto accountManager = DependencyManager::get();
- auto dialogsManager = DependencyManager::get();
- if (!accountManager->isLoggedIn()) {
- Setting::Handle{"loginDialogPoppedUp", false}.set(true);
- dialogsManager->showLoginDialog();
- QJsonObject loginData = {};
- loginData["action"] = "login dialog shown";
- UserActivityLogger::getInstance().logAction("encourageLoginDialog", loginData);
- }
- });
- Setting::Handle{"loginDialogPoppedUp", false}.set(false);
- checkLoginTimer->start();
+ // Do not show login dialog if requested not to on the command line
+ const QString HIFI_NO_LOGIN_COMMAND_LINE_KEY = "--no-login-suggestion";
+ int index = arguments().indexOf(HIFI_NO_LOGIN_COMMAND_LINE_KEY);
+ if (index == -1) {
+ // request not found
+ static int CHECK_LOGIN_TIMER = 3000;
+ QTimer* checkLoginTimer = new QTimer(this);
+ checkLoginTimer->setInterval(CHECK_LOGIN_TIMER);
+ checkLoginTimer->setSingleShot(true);
+ connect(checkLoginTimer, &QTimer::timeout, this, []() {
+ auto accountManager = DependencyManager::get();
+ auto dialogsManager = DependencyManager::get();
+ if (!accountManager->isLoggedIn()) {
+ Setting::Handle{ "loginDialogPoppedUp", false }.set(true);
+ dialogsManager->showLoginDialog();
+ QJsonObject loginData = {};
+ loginData["action"] = "login dialog shown";
+ UserActivityLogger::getInstance().logAction("encourageLoginDialog", loginData);
+ }
+ });
+ Setting::Handle{ "loginDialogPoppedUp", false }.set(false);
+ checkLoginTimer->start();
+ }
#endif
}
@@ -2434,11 +2447,17 @@ QString Application::getUserAgent() {
}
void Application::toggleTabletUI(bool shouldOpen) const {
- auto tabletScriptingInterface = DependencyManager::get();
auto hmd = DependencyManager::get();
if (!(shouldOpen && hmd->getShouldShowTablet())) {
auto HMD = DependencyManager::get();
HMD->toggleShouldShowTablet();
+
+ if (!HMD->getShouldShowTablet()) {
+ DependencyManager::get()->setRaised(false);
+ _window->activateWindow();
+ auto tablet = DependencyManager::get()->getTablet(SYSTEM_TABLET);
+ tablet->unfocus();
+ }
}
}
@@ -2631,6 +2650,8 @@ void Application::cleanupBeforeQuit() {
// it accesses the PickManager to delete its associated Pick
DependencyManager::destroy();
DependencyManager::destroy();
+ DependencyManager::destroy();
+ DependencyManager::destroy();
qCDebug(interfaceapp) << "Application::cleanupBeforeQuit() complete";
}
@@ -2915,6 +2936,7 @@ void Application::initializeRenderEngine() {
// Now that OpenGL is initialized, we are sure we have a valid context and can create the various pipeline shaders with success.
DependencyManager::get()->initializeShapePipelines();
+ DependencyManager::get()->registerKeyboardHighlighting();
});
}
@@ -2927,7 +2949,7 @@ void Application::initializeUi() {
LoginDialog::registerType();
Tooltip::registerType();
UpdateDialog::registerType();
- QmlContextCallback callback = [](QQmlContext* context) {
+ QmlContextCallback commerceCallback = [](QQmlContext* context) {
context->setContextProperty("Commerce", new QmlCommerce());
};
OffscreenQmlSurface::addWhitelistContextHandler({
@@ -2953,7 +2975,13 @@ void Application::initializeUi() {
QUrl{ "hifi/dialogs/security/SecurityImageChange.qml" },
QUrl{ "hifi/dialogs/security/SecurityImageModel.qml" },
QUrl{ "hifi/dialogs/security/SecurityImageSelection.qml" },
- }, callback);
+ }, commerceCallback);
+ QmlContextCallback ttsCallback = [](QQmlContext* context) {
+ context->setContextProperty("TextToSpeech", DependencyManager::get().data());
+ };
+ OffscreenQmlSurface::addWhitelistContextHandler({
+ QUrl{ "hifi/tts/TTS.qml" }
+ }, ttsCallback);
qmlRegisterType("Hifi", 1, 0, "ResourceImageItem");
qmlRegisterType("Hifi", 1, 0, "Preference");
qmlRegisterType("HifiWeb", 1, 0, "WebBrowserSuggestionsEngine");
@@ -3105,6 +3133,7 @@ void Application::onDesktopRootContextCreated(QQmlContext* surfaceContext) {
surfaceContext->setContextProperty("Vec3", new Vec3());
surfaceContext->setContextProperty("Uuid", new ScriptUUID());
surfaceContext->setContextProperty("Assets", DependencyManager::get().data());
+ surfaceContext->setContextProperty("Keyboard", DependencyManager::get().data());
surfaceContext->setContextProperty("AvatarList", DependencyManager::get().data());
surfaceContext->setContextProperty("Users", DependencyManager::get().data());
@@ -6840,6 +6869,8 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe
scriptEngine->registerGlobalObject("LODManager", DependencyManager::get().data());
+ scriptEngine->registerGlobalObject("Keyboard", DependencyManager::get().data());
+
scriptEngine->registerGlobalObject("Paths", DependencyManager::get().data());
scriptEngine->registerGlobalObject("HMD", DependencyManager::get().data());
diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp
index 16e8af5683..1fc1e0c033 100644
--- a/interface/src/Menu.cpp
+++ b/interface/src/Menu.cpp
@@ -356,6 +356,8 @@ Menu::Menu() {
qApp->setHmdTabletBecomesToolbarSetting(action->isChecked());
});
+ addCheckableActionToQMenuAndActionHash(uiOptionsMenu, MenuOption::Use3DKeyboard, 0, true);
+
// Developer > Render >>>
MenuWrapper* renderOptionsMenu = developerMenu->addMenu("Render");
diff --git a/interface/src/Menu.h b/interface/src/Menu.h
index 1e9955a760..f1d56825b5 100644
--- a/interface/src/Menu.h
+++ b/interface/src/Menu.h
@@ -210,6 +210,7 @@ namespace MenuOption {
const QString TurnWithHead = "Turn using Head";
const QString UseAudioForMouth = "Use Audio for Mouth";
const QString UseCamera = "Use Camera";
+ const QString Use3DKeyboard = "Use 3D Keyboard";
const QString VelocityFilter = "Velocity Filter";
const QString VisibleToEveryone = "Everyone";
const QString VisibleToFriends = "Friends";
diff --git a/interface/src/assets/ATPAssetMigrator.cpp b/interface/src/assets/ATPAssetMigrator.cpp
index be7f2014cc..d1f882b232 100644
--- a/interface/src/assets/ATPAssetMigrator.cpp
+++ b/interface/src/assets/ATPAssetMigrator.cpp
@@ -122,8 +122,8 @@ void ATPAssetMigrator::loadEntityServerFile() {
QUrl migrationURL = QUrl(migrationURLString);
if (!_ignoredUrls.contains(migrationURL)
- && (migrationURL.scheme() == URL_SCHEME_HTTP || migrationURL.scheme() == URL_SCHEME_HTTPS
- || migrationURL.scheme() == URL_SCHEME_FILE || migrationURL.scheme() == URL_SCHEME_FTP)) {
+ && (migrationURL.scheme() == HIFI_URL_SCHEME_HTTP || migrationURL.scheme() == HIFI_URL_SCHEME_HTTPS
+ || migrationURL.scheme() == HIFI_URL_SCHEME_FILE || migrationURL.scheme() == HIFI_URL_SCHEME_FTP)) {
if (_pendingReplacements.contains(migrationURL)) {
// we already have a request out for this asset, just store the QJsonValueRef
diff --git a/interface/src/raypick/PickScriptingInterface.cpp b/interface/src/raypick/PickScriptingInterface.cpp
index 26b5aacac5..6e979d2d91 100644
--- a/interface/src/raypick/PickScriptingInterface.cpp
+++ b/interface/src/raypick/PickScriptingInterface.cpp
@@ -31,6 +31,9 @@
#include
+static const float WEB_TOUCH_Y_OFFSET = 0.105f; // how far forward (or back with a negative number) to slide stylus in hand
+static const glm::vec3 TIP_OFFSET = glm::vec3(0.0f, StylusPick::WEB_STYLUS_LENGTH - WEB_TOUCH_Y_OFFSET, 0.0f);
+
unsigned int PickScriptingInterface::createPick(const PickQuery::PickType type, const QVariant& properties) {
switch (type) {
case PickQuery::PickType::Ray:
@@ -137,7 +140,12 @@ unsigned int PickScriptingInterface::createStylusPick(const QVariant& properties
maxDistance = propMap["maxDistance"].toFloat();
}
- return DependencyManager::get()->addPick(PickQuery::Stylus, std::make_shared(side, filter, maxDistance, enabled));
+ glm::vec3 tipOffset = TIP_OFFSET;
+ if (propMap["tipOffset"].isValid()) {
+ tipOffset = vec3FromVariant(propMap["tipOffset"]);
+ }
+
+ return DependencyManager::get()->addPick(PickQuery::Stylus, std::make_shared(side, filter, maxDistance, enabled, tipOffset));
}
// NOTE: Laser pointer still uses scaleWithAvatar. Until scaleWithAvatar is also deprecated for pointers, scaleWithAvatar should not be removed from the pick API.
@@ -416,4 +424,4 @@ void PickScriptingInterface::setParentTransform(std::shared_ptr pick,
pick->parentTransform = std::make_shared(pickID);
}
}
-}
\ No newline at end of file
+}
diff --git a/interface/src/raypick/PointerScriptingInterface.cpp b/interface/src/raypick/PointerScriptingInterface.cpp
index a44d14b4a6..0009536479 100644
--- a/interface/src/raypick/PointerScriptingInterface.cpp
+++ b/interface/src/raypick/PointerScriptingInterface.cpp
@@ -16,6 +16,11 @@
#include "LaserPointer.h"
#include "StylusPointer.h"
#include "ParabolaPointer.h"
+#include "StylusPick.h"
+
+static const glm::quat X_ROT_NEG_90{ 0.70710678f, -0.70710678f, 0.0f, 0.0f };
+static const glm::vec3 DEFAULT_POSITION_OFFSET{0.0f, 0.0f, -StylusPick::WEB_STYLUS_LENGTH / 2.0f};
+static const glm::vec3 DEFAULT_MODEL_DIMENSIONS{0.01f, 0.01f, StylusPick::WEB_STYLUS_LENGTH};
void PointerScriptingInterface::setIgnoreItems(unsigned int uid, const QScriptValue& ignoreItems) const {
DependencyManager::get()->setIgnoreItems(uid, qVectorQUuidFromScriptValue(ignoreItems));
@@ -50,7 +55,17 @@ unsigned int PointerScriptingInterface::createPointer(const PickQuery::PickType&
* @typedef {object} Pointers.StylusPointerProperties
* @property {boolean} [hover=false] If this pointer should generate hover events.
* @property {boolean} [enabled=false]
+ * @property {Vec3} [tipOffset] The specified offset of the from the joint index.
+ * @property {Pointers.StylusPointerProperties.model} [model] Data to replace the default model url, positionOffset and rotationOffset.
*/
+ /**jsdoc
+ * properties defining stylus pick model that can be included to {@link Pointers.StylusPointerProperties}
+ * @typedef {object} Pointers.StylusPointerProperties.model
+ * @property {string} [url] url to the model
+ * @property {Vec3} [dimensions] the dimensions of the model
+ * @property {Vec3} [positionOffset] the position offset of the model from the stylus tip.
+ * @property {Vec3} [rotationOffset] the rotation offset of the model from the parent joint index
+ */
unsigned int PointerScriptingInterface::createStylus(const QVariant& properties) const {
QVariantMap propertyMap = properties.toMap();
@@ -64,7 +79,28 @@ unsigned int PointerScriptingInterface::createStylus(const QVariant& properties)
enabled = propertyMap["enabled"].toBool();
}
- return DependencyManager::get()->addPointer(std::make_shared(properties, StylusPointer::buildStylusOverlay(propertyMap), hover, enabled));
+ glm::vec3 modelPositionOffset = DEFAULT_POSITION_OFFSET;
+ glm::quat modelRotationOffset = X_ROT_NEG_90;
+ glm::vec3 modelDimensions = DEFAULT_MODEL_DIMENSIONS;
+
+ if (propertyMap["model"].isValid()) {
+ QVariantMap modelData = propertyMap["model"].toMap();
+
+ if (modelData["positionOffset"].isValid()) {
+ modelPositionOffset = vec3FromVariant(modelData["positionOffset"]);
+ }
+
+ if (modelData["rotationOffset"].isValid()) {
+ modelRotationOffset = quatFromVariant(modelData["rotationOffset"]);
+ }
+
+ if (modelData["dimensions"].isValid()) {
+ modelDimensions = vec3FromVariant(modelData["dimensions"]);
+ }
+ }
+
+ return DependencyManager::get()->addPointer(std::make_shared(properties, StylusPointer::buildStylusOverlay(propertyMap), hover, enabled, modelPositionOffset,
+ modelRotationOffset, modelDimensions));
}
/**jsdoc
diff --git a/interface/src/raypick/RayPick.cpp b/interface/src/raypick/RayPick.cpp
index ad12db4df2..a48d858504 100644
--- a/interface/src/raypick/RayPick.cpp
+++ b/interface/src/raypick/RayPick.cpp
@@ -118,4 +118,4 @@ glm::vec2 RayPick::projectOntoOverlayXYPlane(const QUuid& overlayID, const glm::
glm::vec2 RayPick::projectOntoEntityXYPlane(const QUuid& entityID, const glm::vec3& worldPos, bool unNormalized) {
auto props = DependencyManager::get()->getEntityProperties(entityID);
return projectOntoXYPlane(worldPos, props.getPosition(), props.getRotation(), props.getDimensions(), props.getRegistrationPoint(), unNormalized);
-}
\ No newline at end of file
+}
diff --git a/interface/src/raypick/StylusPick.cpp b/interface/src/raypick/StylusPick.cpp
index c495ddd194..0a76180be8 100644
--- a/interface/src/raypick/StylusPick.cpp
+++ b/interface/src/raypick/StylusPick.cpp
@@ -21,11 +21,7 @@
#include
using namespace bilateral;
-
-// TODO: make these configurable per pick
-static const float WEB_STYLUS_LENGTH = 0.2f;
-static const float WEB_TOUCH_Y_OFFSET = 0.105f; // how far forward (or back with a negative number) to slide stylus in hand
-static const glm::vec3 TIP_OFFSET = glm::vec3(0.0f, WEB_STYLUS_LENGTH - WEB_TOUCH_Y_OFFSET, 0.0f);
+float StylusPick::WEB_STYLUS_LENGTH = 0.2f;
struct SideData {
QString avatarJoint;
@@ -64,8 +60,8 @@ bool StylusPickResult::checkOrFilterAgainstMaxDistance(float maxDistance) {
return distance < maxDistance;
}
-StylusPick::StylusPick(Side side, const PickFilter& filter, float maxDistance, bool enabled) :
- Pick(StylusTip(side), filter, maxDistance, enabled)
+StylusPick::StylusPick(Side side, const PickFilter& filter, float maxDistance, bool enabled, const glm::vec3& tipOffset) :
+ Pick(StylusTip(side, tipOffset), filter, maxDistance, enabled)
{
}
@@ -90,7 +86,7 @@ static StylusTip getFingerWorldLocation(Side side) {
}
// controllerWorldLocation is where the controller would be, in-world, with an added offset
-static StylusTip getControllerWorldLocation(Side side) {
+static StylusTip getControllerWorldLocation(Side side, const glm::vec3& tipOffset) {
static const std::array INPUTS{ { UserInputMapper::makeStandardInput(SIDES[0].channel),
UserInputMapper::makeStandardInput(SIDES[1].channel) } };
const auto sideIndex = index(side);
@@ -114,7 +110,7 @@ static StylusTip getControllerWorldLocation(Side side) {
// add to the real position so the grab-point is out in front of the hand, a bit
result.position += result.orientation * (sideData.grabPointSphereOffset * sensorScaleFactor);
// move the stylus forward a bit
- result.position += result.orientation * (TIP_OFFSET * sensorScaleFactor);
+ result.position += result.orientation * (tipOffset * sensorScaleFactor);
auto worldControllerPos = avatarPosition + avatarOrientation * pose.translation;
// compute tip velocity from hand controller motion, it is more accurate than computing it from previous positions.
@@ -131,7 +127,7 @@ StylusTip StylusPick::getMathematicalPick() const {
if (qApp->getPreferAvatarFingerOverStylus()) {
result = getFingerWorldLocation(_mathPick.side);
} else {
- result = getControllerWorldLocation(_mathPick.side);
+ result = getControllerWorldLocation(_mathPick.side, _mathPick.tipOffset);
}
return result;
}
@@ -236,4 +232,4 @@ Transform StylusPick::getResultTransform() const {
Transform transform;
transform.setTranslation(stylusResult->intersection);
return transform;
-}
\ No newline at end of file
+}
diff --git a/interface/src/raypick/StylusPick.h b/interface/src/raypick/StylusPick.h
index cd01df20e9..14821c0ce5 100644
--- a/interface/src/raypick/StylusPick.h
+++ b/interface/src/raypick/StylusPick.h
@@ -58,7 +58,7 @@ public:
class StylusPick : public Pick {
using Side = bilateral::Side;
public:
- StylusPick(Side side, const PickFilter& filter, float maxDistance, bool enabled);
+ StylusPick(Side side, const PickFilter& filter, float maxDistance, bool enabled, const glm::vec3& tipOffset);
StylusTip getMathematicalPick() const override;
PickResultPointer getDefaultResult(const QVariantMap& pickVariant) const override;
@@ -71,6 +71,8 @@ public:
bool isLeftHand() const override { return _mathPick.side == Side::Left; }
bool isRightHand() const override { return _mathPick.side == Side::Right; }
bool isMouse() const override { return false; }
+
+ static float WEB_STYLUS_LENGTH;
};
-#endif // hifi_StylusPick_h
\ No newline at end of file
+#endif // hifi_StylusPick_h
diff --git a/interface/src/raypick/StylusPointer.cpp b/interface/src/raypick/StylusPointer.cpp
index 4ba3813c4a..5595c54b71 100644
--- a/interface/src/raypick/StylusPointer.cpp
+++ b/interface/src/raypick/StylusPointer.cpp
@@ -17,9 +17,6 @@
#include "PickScriptingInterface.h"
#include
-// TODO: make these configurable per pointer
-static const float WEB_STYLUS_LENGTH = 0.2f;
-
static const float TABLET_MIN_HOVER_DISTANCE = -0.1f;
static const float TABLET_MAX_HOVER_DISTANCE = 0.1f;
static const float TABLET_MIN_TOUCH_DISTANCE = -0.1f;
@@ -28,9 +25,15 @@ static const float TABLET_MAX_TOUCH_DISTANCE = 0.005f;
static const float HOVER_HYSTERESIS = 0.01f;
static const float TOUCH_HYSTERESIS = 0.001f;
-StylusPointer::StylusPointer(const QVariant& props, const OverlayID& stylusOverlay, bool hover, bool enabled) :
+static const QString DEFAULT_STYLUS_MODEL_URL = PathUtils::resourcesUrl() + "/meshes/tablet-stylus-fat.fbx";
+
+StylusPointer::StylusPointer(const QVariant& props, const OverlayID& stylusOverlay, bool hover, bool enabled,
+ const glm::vec3& modelPositionOffset, const glm::quat& modelRotationOffset, const glm::vec3& modelDimensions) :
Pointer(DependencyManager::get()->createStylusPick(props), enabled, hover),
- _stylusOverlay(stylusOverlay)
+ _stylusOverlay(stylusOverlay),
+ _modelPositionOffset(modelPositionOffset),
+ _modelDimensions(modelDimensions),
+ _modelRotationOffset(modelRotationOffset)
{
}
@@ -42,9 +45,19 @@ StylusPointer::~StylusPointer() {
OverlayID StylusPointer::buildStylusOverlay(const QVariantMap& properties) {
QVariantMap overlayProperties;
+
+ QString modelUrl = DEFAULT_STYLUS_MODEL_URL;
+
+ if (properties["model"].isValid()) {
+ QVariantMap modelData = properties["model"].toMap();
+
+ if (modelData["url"].isValid()) {
+ modelUrl = modelData["url"].toString();
+ }
+ }
// TODO: make these configurable per pointer
overlayProperties["name"] = "stylus";
- overlayProperties["url"] = PathUtils::resourcesUrl() + "/meshes/tablet-stylus-fat.fbx";
+ overlayProperties["url"] = modelUrl;
overlayProperties["loadPriority"] = 10.0f;
overlayProperties["solid"] = true;
overlayProperties["visible"] = false;
@@ -72,13 +85,12 @@ void StylusPointer::updateVisuals(const PickResultPointer& pickResult) {
void StylusPointer::show(const StylusTip& tip) {
if (!_stylusOverlay.isNull()) {
QVariantMap props;
- static const glm::quat X_ROT_NEG_90{ 0.70710678f, -0.70710678f, 0.0f, 0.0f };
- auto modelOrientation = tip.orientation * X_ROT_NEG_90;
+ auto modelOrientation = tip.orientation * _modelRotationOffset;
auto sensorToWorldScale = DependencyManager::get()->getMyAvatar()->getSensorToWorldScale();
- auto modelPositionOffset = modelOrientation * (vec3(0.0f, 0.0f, -WEB_STYLUS_LENGTH / 2.0f) * sensorToWorldScale);
+ auto modelPositionOffset = modelOrientation * (_modelPositionOffset * sensorToWorldScale);
props["position"] = vec3toVariant(tip.position + modelPositionOffset);
props["rotation"] = quatToVariant(modelOrientation);
- props["dimensions"] = vec3toVariant(sensorToWorldScale * vec3(0.01f, 0.01f, WEB_STYLUS_LENGTH));
+ props["dimensions"] = vec3toVariant(sensorToWorldScale * _modelDimensions);
props["visible"] = true;
qApp->getOverlays().editOverlay(_stylusOverlay, props);
}
diff --git a/interface/src/raypick/StylusPointer.h b/interface/src/raypick/StylusPointer.h
index ff60fd78e5..64e2a38bed 100644
--- a/interface/src/raypick/StylusPointer.h
+++ b/interface/src/raypick/StylusPointer.h
@@ -21,7 +21,8 @@ class StylusPointer : public Pointer {
using Ptr = std::shared_ptr;
public:
- StylusPointer(const QVariant& props, const OverlayID& stylusOverlay, bool hover, bool enabled);
+ StylusPointer(const QVariant& props, const OverlayID& stylusOverlay, bool hover, bool enabled,
+ const glm::vec3& modelPositionOffset, const glm::quat& modelRotationOffset, const glm::vec3& modelDimensions);
~StylusPointer();
void updateVisuals(const PickResultPointer& pickResult) override;
@@ -81,6 +82,10 @@ private:
bool _showing { true };
+ glm::vec3 _modelPositionOffset;
+ glm::vec3 _modelDimensions;
+ glm::quat _modelRotationOffset;
+
};
#endif // hifi_StylusPointer_h
diff --git a/interface/src/scripting/HMDScriptingInterface.cpp b/interface/src/scripting/HMDScriptingInterface.cpp
index ea24d6c793..f2f8d3b8d4 100644
--- a/interface/src/scripting/HMDScriptingInterface.cpp
+++ b/interface/src/scripting/HMDScriptingInterface.cpp
@@ -119,8 +119,11 @@ void HMDScriptingInterface::toggleShouldShowTablet() {
}
void HMDScriptingInterface::setShouldShowTablet(bool value) {
- _showTablet = value;
- _tabletContextualMode = false;
+ if (_showTablet != value) {
+ _showTablet = value;
+ _tabletContextualMode = false;
+ emit showTabletChanged(value);
+ }
}
QScriptValue HMDScriptingInterface::getHUDLookAtPosition2D(QScriptContext* context, QScriptEngine* engine) {
diff --git a/interface/src/scripting/HMDScriptingInterface.h b/interface/src/scripting/HMDScriptingInterface.h
index 2c0a3fe45f..6cc695762b 100644
--- a/interface/src/scripting/HMDScriptingInterface.h
+++ b/interface/src/scripting/HMDScriptingInterface.h
@@ -355,6 +355,8 @@ signals:
*/
bool shouldShowHandControllersChanged();
+ void showTabletChanged(bool showTablet);
+
public:
HMDScriptingInterface();
static QScriptValue getHUDLookAtPosition2D(QScriptContext* context, QScriptEngine* engine);
diff --git a/interface/src/scripting/KeyboardScriptingInterface.cpp b/interface/src/scripting/KeyboardScriptingInterface.cpp
new file mode 100644
index 0000000000..b26e1ec378
--- /dev/null
+++ b/interface/src/scripting/KeyboardScriptingInterface.cpp
@@ -0,0 +1,34 @@
+//
+// KeyboardScriptingInterface.cpp
+// interface/src/scripting
+//
+// Created by Dante Ruiz on 2018-08-27.
+// Copyright 2017 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#include "KeyboardScriptingInterface.h"
+#include "ui/Keyboard.h"
+
+bool KeyboardScriptingInterface::isRaised() {
+ return DependencyManager::get()->isRaised();
+}
+
+void KeyboardScriptingInterface::setRaised(bool raised) {
+ DependencyManager::get()->setRaised(raised);
+}
+
+
+bool KeyboardScriptingInterface::isPassword() {
+ return DependencyManager::get()->isPassword();
+}
+
+void KeyboardScriptingInterface::setPassword(bool password) {
+ DependencyManager::get()->setPassword(password);
+}
+
+void KeyboardScriptingInterface::loadKeyboardFile(const QString& keyboardFile) {
+ DependencyManager::get()->loadKeyboardFile(keyboardFile);
+}
diff --git a/interface/src/scripting/KeyboardScriptingInterface.h b/interface/src/scripting/KeyboardScriptingInterface.h
new file mode 100644
index 0000000000..1ab91ea7c3
--- /dev/null
+++ b/interface/src/scripting/KeyboardScriptingInterface.h
@@ -0,0 +1,43 @@
+//
+// KeyboardScriptingInterface.h
+// interface/src/scripting
+//
+// Created by Dante Ruiz on 2018-08-27.
+// Copyright 2017 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#ifndef hifi_KeyboardScriptingInterface_h
+#define hifi_KeyboardScriptingInterface_h
+
+#include
+
+#include "DependencyManager.h"
+
+/**jsdoc
+ * The Keyboard API provides facilities to use 3D Physical keyboard.
+ * @namespace Keyboard
+ *
+ * @hifi-interface
+ * @hifi-client-entity
+ *
+ * @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)
+
+public:
+ Q_INVOKABLE void loadKeyboardFile(const QString& string);
+private:
+ bool isRaised();
+ void setRaised(bool raised);
+
+ bool isPassword();
+ void setPassword(bool password);
+};
+#endif
diff --git a/interface/src/scripting/TTSScriptingInterface.cpp b/interface/src/scripting/TTSScriptingInterface.cpp
new file mode 100644
index 0000000000..6b1677aecb
--- /dev/null
+++ b/interface/src/scripting/TTSScriptingInterface.cpp
@@ -0,0 +1,163 @@
+//
+// TTSScriptingInterface.cpp
+// libraries/audio-client/src/scripting
+//
+// Created by Zach Fox on 2018-10-10.
+// 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 "TTSScriptingInterface.h"
+#include "avatar/AvatarManager.h"
+
+TTSScriptingInterface::TTSScriptingInterface() {
+#ifdef WIN32
+ //
+ // Create text to speech engine
+ //
+ HRESULT hr = m_tts.CoCreateInstance(CLSID_SpVoice);
+ if (FAILED(hr)) {
+ qDebug() << "Text-to-speech engine creation failed.";
+ }
+
+ //
+ // Get token corresponding to default voice
+ //
+ hr = SpGetDefaultTokenFromCategoryId(SPCAT_VOICES, &m_voiceToken, FALSE);
+ if (FAILED(hr)) {
+ qDebug() << "Can't get default voice token.";
+ }
+
+ //
+ // Set default voice
+ //
+ hr = m_tts->SetVoice(m_voiceToken);
+ if (FAILED(hr)) {
+ qDebug() << "Can't set default voice.";
+ }
+
+ _lastSoundAudioInjectorUpdateTimer.setSingleShot(true);
+ connect(&_lastSoundAudioInjectorUpdateTimer, &QTimer::timeout, this, &TTSScriptingInterface::updateLastSoundAudioInjector);
+#endif
+}
+
+TTSScriptingInterface::~TTSScriptingInterface() {
+}
+
+#ifdef WIN32
+class ReleaseOnExit {
+public:
+ ReleaseOnExit(IUnknown* p) : m_p(p) {}
+ ~ReleaseOnExit() {
+ if (m_p) {
+ m_p->Release();
+ }
+ }
+
+private:
+ IUnknown* m_p;
+};
+#endif
+
+const int INJECTOR_INTERVAL_MS = 100;
+void TTSScriptingInterface::updateLastSoundAudioInjector() {
+ if (_lastSoundAudioInjector) {
+ AudioInjectorOptions options;
+ options.position = DependencyManager::get()->getMyAvatarPosition();
+ _lastSoundAudioInjector->setOptions(options);
+ _lastSoundAudioInjectorUpdateTimer.start(INJECTOR_INTERVAL_MS);
+ }
+}
+
+void TTSScriptingInterface::speakText(const QString& textToSpeak) {
+#ifdef WIN32
+ WAVEFORMATEX fmt;
+ fmt.wFormatTag = WAVE_FORMAT_PCM;
+ fmt.nSamplesPerSec = AudioConstants::SAMPLE_RATE;
+ fmt.wBitsPerSample = 16;
+ fmt.nChannels = 1;
+ fmt.nBlockAlign = fmt.nChannels * fmt.wBitsPerSample / 8;
+ fmt.nAvgBytesPerSec = fmt.nSamplesPerSec * fmt.nBlockAlign;
+ fmt.cbSize = 0;
+
+ IStream* pStream = NULL;
+
+ ISpStream* pSpStream = nullptr;
+ HRESULT hr = CoCreateInstance(CLSID_SpStream, nullptr, CLSCTX_ALL, __uuidof(ISpStream), (void**)&pSpStream);
+ if (FAILED(hr)) {
+ qDebug() << "CoCreateInstance failed.";
+ }
+ ReleaseOnExit rSpStream(pSpStream);
+
+ pStream = SHCreateMemStream(NULL, 0);
+ if (nullptr == pStream) {
+ qDebug() << "SHCreateMemStream failed.";
+ }
+
+ hr = pSpStream->SetBaseStream(pStream, SPDFID_WaveFormatEx, &fmt);
+ if (FAILED(hr)) {
+ qDebug() << "Can't set base stream.";
+ }
+
+ hr = m_tts->SetOutput(pSpStream, true);
+ if (FAILED(hr)) {
+ qDebug() << "Can't set output stream.";
+ }
+
+ ReleaseOnExit rStream(pStream);
+
+ ULONG streamNumber;
+ hr = m_tts->Speak(reinterpret_cast(textToSpeak.utf16()), SPF_IS_XML | SPF_ASYNC | SPF_PURGEBEFORESPEAK,
+ &streamNumber);
+ if (FAILED(hr)) {
+ qDebug() << "Speak failed.";
+ }
+
+ m_tts->WaitUntilDone(-1);
+
+ hr = pSpStream->GetBaseStream(&pStream);
+ if (FAILED(hr)) {
+ qDebug() << "Couldn't get base stream.";
+ }
+
+ hr = IStream_Reset(pStream);
+ if (FAILED(hr)) {
+ qDebug() << "Couldn't reset stream.";
+ }
+
+ ULARGE_INTEGER StreamSize;
+ StreamSize.LowPart = 0;
+ hr = IStream_Size(pStream, &StreamSize);
+
+ DWORD dwSize = StreamSize.QuadPart;
+ _lastSoundByteArray.resize(dwSize);
+
+ hr = IStream_Read(pStream, _lastSoundByteArray.data(), dwSize);
+ if (FAILED(hr)) {
+ qDebug() << "Couldn't read from stream.";
+ }
+
+ AudioInjectorOptions options;
+ options.position = DependencyManager::get()->getMyAvatarPosition();
+
+ if (_lastSoundAudioInjector) {
+ _lastSoundAudioInjector->stop();
+ _lastSoundAudioInjectorUpdateTimer.stop();
+ }
+
+ _lastSoundAudioInjector = AudioInjector::playSoundAndDelete(_lastSoundByteArray, options);
+
+ _lastSoundAudioInjectorUpdateTimer.start(INJECTOR_INTERVAL_MS);
+#else
+ qDebug() << "Text-to-Speech isn't currently supported on non-Windows platforms.";
+#endif
+}
+
+void TTSScriptingInterface::stopLastSpeech() {
+ if (_lastSoundAudioInjector) {
+ _lastSoundAudioInjector->stop();
+ _lastSoundAudioInjector = NULL;
+ }
+}
diff --git a/interface/src/scripting/TTSScriptingInterface.h b/interface/src/scripting/TTSScriptingInterface.h
new file mode 100644
index 0000000000..0f1e723885
--- /dev/null
+++ b/interface/src/scripting/TTSScriptingInterface.h
@@ -0,0 +1,88 @@
+// TTSScriptingInterface.h
+// libraries/audio-client/src/scripting
+//
+// Created by Zach Fox on 2018-10-10.
+// 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
+//
+
+#ifndef hifi_SpeechScriptingInterface_h
+#define hifi_SpeechScriptingInterface_h
+
+#include
+#include
+#include
+#ifdef WIN32
+#pragma warning(disable : 4996)
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+#include // SAPI
+#include // SAPI Helper
+#endif
+#include
+#include
+
+class TTSScriptingInterface : public QObject, public Dependency {
+ Q_OBJECT
+
+public:
+ TTSScriptingInterface();
+ ~TTSScriptingInterface();
+
+ Q_INVOKABLE void speakText(const QString& textToSpeak);
+ Q_INVOKABLE void stopLastSpeech();
+
+private:
+#ifdef WIN32
+ class CComAutoInit {
+ public:
+ // Initializes COM using CoInitialize.
+ // On failure, signals error using AtlThrow.
+ CComAutoInit() {
+ HRESULT hr = ::CoInitialize(NULL);
+ if (FAILED(hr)) {
+ ATLTRACE(TEXT("CoInitialize() failed in CComAutoInit constructor (hr=0x%08X).\n"), hr);
+ AtlThrow(hr);
+ }
+ }
+
+ // Initializes COM using CoInitializeEx.
+ // On failure, signals error using AtlThrow.
+ explicit CComAutoInit(__in DWORD dwCoInit) {
+ HRESULT hr = ::CoInitializeEx(NULL, dwCoInit);
+ if (FAILED(hr)) {
+ ATLTRACE(TEXT("CoInitializeEx() failed in CComAutoInit constructor (hr=0x%08X).\n"), hr);
+ AtlThrow(hr);
+ }
+ }
+
+ // Uninitializes COM using CoUninitialize.
+ ~CComAutoInit() { ::CoUninitialize(); }
+
+ //
+ // Ban copy
+ //
+ private:
+ CComAutoInit(const CComAutoInit&);
+ };
+
+ // COM initialization and cleanup (must precede other COM related data members)
+ CComAutoInit m_comInit;
+
+ // Text to speech engine
+ CComPtr m_tts;
+
+ // Default voice token
+ CComPtr m_voiceToken;
+#endif
+
+ QByteArray _lastSoundByteArray;
+ AudioInjectorPointer _lastSoundAudioInjector;
+ QTimer _lastSoundAudioInjectorUpdateTimer;
+ void updateLastSoundAudioInjector();
+};
+
+#endif // hifi_SpeechScriptingInterface_h
diff --git a/interface/src/ui/Keyboard.cpp b/interface/src/ui/Keyboard.cpp
new file mode 100644
index 0000000000..773253f85c
--- /dev/null
+++ b/interface/src/ui/Keyboard.cpp
@@ -0,0 +1,882 @@
+//
+// 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
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#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 calculateKeyboardPositionAndOrientation() {
+ auto myAvatar = DependencyManager::get()->getMyAvatar();
+ auto hmd = DependencyManager::get();
+
+ std::pair 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(overlays.getOverlay(tabletID));
+ if (tabletOverlay) {
+ auto tablet = DependencyManager::get()->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(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(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();
+ auto windowScriptingInterface = DependencyManager::get();
+ auto myAvatar = DependencyManager::get()->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();
+ 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();
+
+ 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(leftStylusProperties, StylusPointer::buildStylusOverlay(leftStylusProperties), true, true,
+ MALLET_POSITION_OFFSET, MALLET_ROTATION_OFFSET, MALLET_MODEL_DIMENSIONS));
+ _rightHandStylus = pointerManager->addPointer(std::make_shared(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()->getSound(SOUND_FILE);
+}
+
+bool Keyboard::isRaised() const {
+ return resultWithReadLock([&] { 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()->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(overlays.getOverlay(anchorOverlayID));
+ if (anchorOverlay) {
+ std::pair 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(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(overlays.getOverlay(iter.key()));
+ if (base3DOverlay) {
+ base3DOverlay->setVisible(raise);
+ }
+ }
+}
+
+bool Keyboard::isPassword() const {
+ return resultWithReadLock([&] { 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(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(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->triggerHapticPulse(PULSE_STRENGTH, PULSE_DURATION, handIndex);
+
+ Overlays& overlays = qApp->getOverlays();
+ auto base3DOverlay = std::dynamic_pointer_cast(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()->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();
+ 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(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();
+ 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(overlays.getOverlay(overlayID));
+
+ if (base3DOverlay) {
+ auto pointerManager = DependencyManager::get();
+ auto pickResult = pointerManager->getPrevPickResult(pointerID);
+ auto stylusPickResult = std::dynamic_pointer_cast(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();
+ 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();
+ selection->removeFromSelectedItemsList(KEY_HOVER_HIGHLIGHT, "overlay", overlayID);
+}
+
+void Keyboard::disableStylus() {
+ auto pointerManager = DependencyManager::get();
+ 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()->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 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 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.push_back(_textDisplay.overlayID);
+ _itemsToIgnore.push_back(_anchor.overlayID);
+ });
+ _layerIndex = 0;
+ auto pointerManager = DependencyManager::get();
+ pointerManager->setIncludeItems(_leftHandStylus, includeItems);
+ pointerManager->setIncludeItems(_rightHandStylus, includeItems);
+ });
+
+ request->send();
+}
+
+QVector Keyboard::getKeysID() {
+ return _ignoreItemsLock.resultWithReadLock>([&] {
+ 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->setRenderState(_leftHandStylus, "events on");
+ pointerManager->enablePointer(_leftHandStylus);
+ pointerManager->setRenderState(_rightHandStylus, "events on");
+ pointerManager->enablePointer(_rightHandStylus);
+
+}
diff --git a/interface/src/ui/Keyboard.h b/interface/src/ui/Keyboard.h
new file mode 100644
index 0000000000..18db38b2ae
--- /dev/null
+++ b/interface/src/ui/Keyboard.h
@@ -0,0 +1,155 @@
+//
+// Keyboard.h
+// interface/src/scripting
+//
+// Created by Dante Ruiz on 2018-08-27.
+// Copyright 2017 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#ifndef hifi_Keyboard_h
+#define hifi_Keyboard_h
+
+#include