Merge pull request #8068 from highfidelity/vive-ui

Vive UI Snapshot
This commit is contained in:
Howard Stearns 2016-06-23 10:27:30 -07:00 committed by GitHub
commit 64771428ec
104 changed files with 4283 additions and 2722 deletions

View file

@ -1,18 +1,26 @@
{
"name": "Vive to Standard",
"channels": [
{ "from": "Vive.LY", "when": "Vive.LSOuter", "filters": ["invert"], "to": "Standard.LY" },
{ "from": "Vive.LX", "when": "Vive.LSOuter", "to": "Standard.LX" },
{ "from": "Vive.LT", "to": "Standard.LT" },
{ "from": "Vive.LY", "when": "Vive.LSY", "filters": ["invert"], "to": "Standard.LY" },
{ "from": "Vive.LX", "when": "Vive.LSX", "to": "Standard.LX" },
{
"from": "Vive.LT", "to": "Standard.LT",
"filters": [
{ "type": "deadZone", "min": 0.05 }
]
},
{ "from": "Vive.LeftGrip", "to": "Standard.LeftGrip" },
{ "from": "Vive.LS", "to": "Standard.LS" },
{ "from": "Vive.LSTouch", "to": "Standard.LSTouch" },
{ "from": "Vive.RY", "when": "Vive.RSOuter", "filters": ["invert"], "to": "Standard.RY" },
{ "from": "Vive.RX", "when": "Vive.RSOuter", "to": "Standard.RX" },
{ "from": "Vive.RT", "to": "Standard.RT" },
{ "from": "Vive.RY", "when": "Vive.RSY", "filters": ["invert"], "to": "Standard.RY" },
{ "from": "Vive.RX", "when": "Vive.RSX", "to": "Standard.RX" },
{
"from": "Vive.RT", "to": "Standard.RT",
"filters": [
{ "type": "deadZone", "min": 0.05 }
]
},
{ "from": "Vive.RightGrip", "to": "Standard.RightGrip" },
{ "from": "Vive.RS", "to": "Standard.RS" },
{ "from": "Vive.RSTouch", "to": "Standard.RSTouch" },

View file

@ -0,0 +1,105 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 50 150" style="enable-background:new 0 0 50 150;" xml:space="preserve">
<style type="text/css">
.st0{opacity:0.49;}
.st1{fill:#1E1E1E;}
.st2{opacity:0.5;}
.st3{fill:#EAEAEA;}
.st4{fill:#FFFFFF;}
</style>
<g class="st0">
<path class="st1" d="M50,146c0,2.2-1.8,4-4,4H4c-2.2,0-4-1.8-4-4v-42c0-2.2,1.8-4,4-4h42c2.2,0,4,1.8,4,4V146z"/>
</g>
<g class="st2">
<g>
<path class="st3" d="M25.1,109.5c-4.2,0-8,1.8-10.8,4.8c0,0-0.5,0.7,0.2,1.2c0.7,0.6,1.4-0.1,1.4-0.1c2.4-2.4,5.7-3.9,9.2-3.9
c3.6,0,6.9,1.5,9.3,4.1c0,0,0.7,0.8,1.5,0.3c0.7-0.4,0.2-1.2,0.2-1.3C33.3,111.4,29.4,109.5,25.1,109.5z"/>
<path class="st3" d="M35.4,116.3c-0.2,0-0.4,0-0.5-0.1c-0.2-0.1-0.5-0.3-0.6-0.4l0,0c-2.5-2.6-5.7-4-9.2-4c-3.4,0-6.6,1.4-9.1,3.9
l0,0c-0.1,0.1-0.3,0.2-0.4,0.3c-0.5,0.2-0.9,0.2-1.3-0.2c-0.6-0.5-0.4-1.1-0.3-1.4c0-0.1,0.1-0.1,0.1-0.2l0,0
c2.9-3.1,6.8-4.9,10.9-4.9c4.2,0,8.2,1.8,11.2,5.1c0.1,0.1,0.3,0.5,0.2,0.9c0,0.3-0.2,0.6-0.5,0.8
C35.9,116.2,35.6,116.3,35.4,116.3z M34.7,115.4c0.1,0.1,0.6,0.6,1.1,0.2c0.1-0.1,0.2-0.2,0.2-0.3c0-0.3-0.1-0.5-0.1-0.6
c-2.9-3.2-6.7-5-10.8-5c-4,0-7.7,1.7-10.6,4.7c-0.1,0.1-0.3,0.5,0.1,0.9c0.5,0.4,1,0,1.1-0.1c2.6-2.6,5.9-4,9.4-4
C28.7,111.2,32.1,112.7,34.7,115.4L34.7,115.4z M36,114.7C36,114.7,36,114.7,36,114.7L36,114.7z M35.9,114.7
C35.9,114.7,35.9,114.7,35.9,114.7L35.9,114.7z"/>
</g>
<path class="st3" d="M27.3,116l-1.5-0.9c-0.3-0.2-0.7-0.2-1,0l-1.5,0.9c-0.3,0.2-0.5,0.5-0.5,0.9l0,1.8c0,0.4,0.2,0.7,0.5,0.9
l1.5,0.9c0.2,0.1,0.3,0.1,0.5,0.1c0.2,0,0.4,0,0.5-0.1l1.5-0.9c0.3-0.2,0.5-0.5,0.5-0.9l0-1.8C27.8,116.5,27.6,116.2,27.3,116z"/>
<path class="st3" d="M31.2,125l-1.2-1.5c-0.2-0.3-0.5-0.4-0.8-0.4l-7.9,0c0,0,0,0,0,0c-0.3,0-0.6,0.1-0.8,0.4l-1.2,1.5
c-0.3,0.4-0.3,0.9,0,1.3l1.2,1.5c0.2,0.3,0.5,0.4,0.8,0.4l7.9,0c0,0,0,0,0,0c0.3,0,0.6-0.1,0.8-0.4l1.2-1.5
C31.5,126,31.5,125.4,31.2,125z"/>
</g>
<g class="st2">
<path class="st3" d="M19.2,135.5v6.4H18v-2.7h-2.9v2.7h-1.2v-6.4h1.2v2.6H18v-2.6H19.2z"/>
<path class="st3" d="M25.4,141c0.3,0,0.6-0.1,0.8-0.2c0.2-0.1,0.4-0.3,0.5-0.5c0.1-0.2,0.2-0.4,0.3-0.7c0.1-0.3,0.1-0.5,0.1-0.8
v-3.4h1.3v3.4c0,0.5-0.1,0.9-0.2,1.3c-0.1,0.4-0.3,0.7-0.5,1c-0.2,0.3-0.5,0.5-0.9,0.7c-0.4,0.2-0.8,0.3-1.3,0.3
c-0.5,0-1-0.1-1.4-0.3c-0.4-0.2-0.7-0.4-0.9-0.7c-0.2-0.3-0.4-0.6-0.5-1c-0.1-0.4-0.2-0.8-0.2-1.2v-3.4h1.3v3.4
c0,0.3,0,0.5,0.1,0.8c0.1,0.3,0.1,0.5,0.3,0.7c0.1,0.2,0.3,0.4,0.5,0.5S25.1,141,25.4,141z"/>
<path class="st3" d="M31.7,141.9v-6.4H34c0.5,0,1,0.1,1.4,0.3c0.4,0.2,0.7,0.4,1,0.7c0.3,0.3,0.5,0.6,0.6,1
c0.1,0.4,0.2,0.8,0.2,1.2c0,0.5-0.1,0.9-0.2,1.3c-0.1,0.4-0.4,0.7-0.6,1c-0.3,0.3-0.6,0.5-1,0.6c-0.4,0.2-0.8,0.2-1.3,0.2H31.7z
M35.9,138.7c0-0.3,0-0.6-0.1-0.8c-0.1-0.3-0.2-0.5-0.4-0.7c-0.2-0.2-0.4-0.3-0.6-0.4c-0.2-0.1-0.5-0.2-0.8-0.2h-1.1v4.2H34
c0.3,0,0.6-0.1,0.8-0.2c0.2-0.1,0.4-0.3,0.6-0.4c0.2-0.2,0.3-0.4,0.4-0.7C35.8,139.3,35.9,139,35.9,138.7z"/>
</g>
<g>
<path class="st1" d="M50,96c0,2.2-1.8,4-4,4H4c-2.2,0-4-1.8-4-4V54c0-2.2,1.8-4,4-4h42c2.2,0,4,1.8,4,4V96z"/>
</g>
<g>
<g>
<path class="st3" d="M25.1,59.5c-4.2,0-8,1.8-10.8,4.8c0,0-0.5,0.7,0.2,1.2c0.7,0.6,1.4-0.1,1.4-0.1c2.4-2.4,5.7-3.9,9.2-3.9
c3.6,0,6.9,1.5,9.3,4.1c0,0,0.7,0.8,1.5,0.3c0.7-0.4,0.2-1.2,0.2-1.3C33.3,61.5,29.4,59.5,25.1,59.5z"/>
<path class="st3" d="M35.4,66.3c-0.2,0-0.4,0-0.5-0.1c-0.2-0.1-0.5-0.3-0.6-0.4l0,0c-2.5-2.6-5.7-4-9.2-4c-3.4,0-6.6,1.4-9.1,3.9
l0,0c-0.1,0.1-0.3,0.2-0.4,0.3c-0.5,0.2-0.9,0.2-1.3-0.2c-0.6-0.5-0.4-1.1-0.3-1.4c0-0.1,0.1-0.1,0.1-0.2l0,0
c2.9-3.1,6.8-4.9,10.9-4.9c4.2,0,8.2,1.8,11.2,5.1c0.1,0.1,0.3,0.5,0.2,0.9c0,0.3-0.2,0.6-0.5,0.8C35.9,66.2,35.6,66.3,35.4,66.3z
M34.7,65.5c0.1,0.1,0.6,0.6,1.1,0.2c0.1-0.1,0.2-0.2,0.2-0.3c0-0.3-0.1-0.5-0.1-0.6c-2.9-3.2-6.7-5-10.8-5c-4,0-7.7,1.7-10.6,4.7
c-0.1,0.1-0.3,0.5,0.1,0.9c0.5,0.4,1,0,1.1-0.1c2.6-2.6,5.9-4,9.4-4C28.7,61.3,32.1,62.7,34.7,65.5L34.7,65.5z M36,64.7
C36,64.7,36,64.7,36,64.7L36,64.7z M35.9,64.7C35.9,64.7,35.9,64.7,35.9,64.7L35.9,64.7z"/>
</g>
<path class="st3" d="M27.3,66l-1.5-0.9c-0.3-0.2-0.7-0.2-1,0L23.2,66c-0.3,0.2-0.5,0.5-0.5,0.9l0,1.8c0,0.4,0.2,0.7,0.5,0.9
l1.5,0.9c0.2,0.1,0.3,0.1,0.5,0.1c0.2,0,0.4,0,0.5-0.1l1.5-0.9c0.3-0.2,0.5-0.5,0.5-0.9l0-1.8C27.8,66.5,27.6,66.2,27.3,66z"/>
<path class="st3" d="M31.2,75.1L30,73.5c-0.2-0.3-0.5-0.4-0.8-0.4l-7.9,0c0,0,0,0,0,0c-0.3,0-0.6,0.1-0.8,0.4l-1.2,1.5
c-0.3,0.4-0.3,0.9,0,1.3l1.2,1.5c0.2,0.3,0.5,0.4,0.8,0.4l7.9,0c0,0,0,0,0,0c0.3,0,0.6-0.1,0.8-0.4l1.2-1.5
C31.5,76,31.5,75.5,31.2,75.1z"/>
</g>
<g>
<path class="st3" d="M19.2,85.5v6.4H18v-2.7h-2.9v2.7h-1.2v-6.4h1.2v2.6H18v-2.6H19.2z"/>
<path class="st3" d="M25.4,91c0.3,0,0.6-0.1,0.8-0.2s0.4-0.3,0.5-0.5c0.1-0.2,0.2-0.4,0.3-0.7s0.1-0.5,0.1-0.8v-3.4h1.3v3.4
c0,0.5-0.1,0.9-0.2,1.3c-0.1,0.4-0.3,0.7-0.5,1c-0.2,0.3-0.5,0.5-0.9,0.7c-0.4,0.2-0.8,0.3-1.3,0.3c-0.5,0-1-0.1-1.4-0.3
c-0.4-0.2-0.7-0.4-0.9-0.7c-0.2-0.3-0.4-0.6-0.5-1c-0.1-0.4-0.2-0.8-0.2-1.2v-3.4h1.3v3.4c0,0.3,0,0.5,0.1,0.8
c0.1,0.3,0.1,0.5,0.3,0.7c0.1,0.2,0.3,0.4,0.5,0.5S25.1,91,25.4,91z"/>
<path class="st3" d="M31.7,91.9v-6.4H34c0.5,0,1,0.1,1.4,0.3c0.4,0.2,0.7,0.4,1,0.7c0.3,0.3,0.5,0.6,0.6,1c0.1,0.4,0.2,0.8,0.2,1.2
c0,0.5-0.1,0.9-0.2,1.3c-0.1,0.4-0.4,0.7-0.6,1c-0.3,0.3-0.6,0.5-1,0.6c-0.4,0.2-0.8,0.2-1.3,0.2H31.7z M35.9,88.7
c0-0.3,0-0.6-0.1-0.8c-0.1-0.3-0.2-0.5-0.4-0.7c-0.2-0.2-0.4-0.3-0.6-0.4c-0.2-0.1-0.5-0.2-0.8-0.2h-1.1v4.2H34
c0.3,0,0.6-0.1,0.8-0.2c0.2-0.1,0.4-0.3,0.6-0.4c0.2-0.2,0.3-0.4,0.4-0.7C35.8,89.3,35.9,89,35.9,88.7z"/>
</g>
<g>
<path class="st4" d="M50,46.1c0,2.2-1.8,4-4,4H4c-2.2,0-4-1.8-4-4v-42c0-2.2,1.8-4,4-4h42c2.2,0,4,1.8,4,4V46.1z"/>
</g>
<g>
<g>
<path d="M25.1,9.6c-4.2,0-8,1.8-10.8,4.8c0,0-0.5,0.7,0.2,1.2c0.7,0.6,1.4-0.1,1.4-0.1c2.4-2.4,5.7-3.9,9.2-3.9
c3.6,0,6.9,1.5,9.3,4.1c0,0,0.7,0.8,1.5,0.3c0.7-0.4,0.2-1.2,0.2-1.3C33.3,11.5,29.4,9.6,25.1,9.6z"/>
<path d="M35.4,16.3c-0.2,0-0.4,0-0.5-0.1c-0.2-0.1-0.5-0.3-0.6-0.4l0,0c-2.5-2.6-5.7-4-9.2-4c-3.4,0-6.6,1.4-9.1,3.9l0,0
c-0.1,0.1-0.3,0.2-0.4,0.3c-0.5,0.2-0.9,0.2-1.3-0.2c-0.6-0.5-0.4-1.1-0.3-1.4c0-0.1,0.1-0.1,0.1-0.2l0,0
c2.9-3.1,6.8-4.9,10.9-4.9c4.2,0,8.2,1.8,11.2,5.1c0.1,0.1,0.3,0.5,0.2,0.9c0,0.3-0.2,0.6-0.5,0.8C35.9,16.3,35.6,16.3,35.4,16.3z
M34.7,15.5c0.1,0.1,0.6,0.6,1.1,0.2c0.1-0.1,0.2-0.2,0.2-0.3c0-0.3-0.1-0.5-0.1-0.6c-2.9-3.2-6.7-5-10.8-5c-4,0-7.7,1.7-10.6,4.7
c-0.1,0.1-0.3,0.5,0.1,0.9c0.5,0.4,1,0,1.1-0.1c2.6-2.6,5.9-4,9.4-4C28.7,11.3,32.1,12.8,34.7,15.5L34.7,15.5z M36,14.8
C36,14.8,36,14.8,36,14.8L36,14.8z M35.9,14.8C35.9,14.8,35.9,14.8,35.9,14.8L35.9,14.8z"/>
</g>
<path d="M27.3,16l-1.5-0.9c-0.3-0.2-0.7-0.2-1,0L23.2,16c-0.3,0.2-0.5,0.5-0.5,0.9l0,1.8c0,0.4,0.2,0.7,0.5,0.9l1.5,0.9
c0.2,0.1,0.3,0.1,0.5,0.1c0.2,0,0.4,0,0.5-0.1l1.5-0.9c0.3-0.2,0.5-0.5,0.5-0.9l0-1.8C27.8,16.6,27.6,16.2,27.3,16z"/>
<path d="M31.2,25.1L30,23.6c-0.2-0.3-0.5-0.4-0.8-0.4l-7.9,0c0,0,0,0,0,0c-0.3,0-0.6,0.1-0.8,0.4l-1.2,1.5c-0.3,0.4-0.3,0.9,0,1.3
l1.2,1.5c0.2,0.3,0.5,0.4,0.8,0.4l7.9,0c0,0,0,0,0,0c0.3,0,0.6-0.1,0.8-0.4l1.2-1.5C31.5,26,31.5,25.5,31.2,25.1z"/>
</g>
<g>
<path d="M19.2,35.6V42H18v-2.7h-2.9V42h-1.2v-6.4h1.2v2.6H18v-2.6H19.2z"/>
<path d="M25.4,41c0.3,0,0.6-0.1,0.8-0.2c0.2-0.1,0.4-0.3,0.5-0.5c0.1-0.2,0.2-0.4,0.3-0.7c0.1-0.3,0.1-0.5,0.1-0.8v-3.4h1.3v3.4
c0,0.5-0.1,0.9-0.2,1.3c-0.1,0.4-0.3,0.7-0.5,1c-0.2,0.3-0.5,0.5-0.9,0.7c-0.4,0.2-0.8,0.3-1.3,0.3c-0.5,0-1-0.1-1.4-0.3
c-0.4-0.2-0.7-0.4-0.9-0.7c-0.2-0.3-0.4-0.6-0.5-1c-0.1-0.4-0.2-0.8-0.2-1.2v-3.4h1.3v3.4c0,0.3,0,0.5,0.1,0.8
c0.1,0.3,0.1,0.5,0.3,0.7c0.1,0.2,0.3,0.4,0.5,0.5C24.9,41,25.1,41,25.4,41z"/>
<path d="M31.7,42v-6.4H34c0.5,0,1,0.1,1.4,0.3c0.4,0.2,0.7,0.4,1,0.7c0.3,0.3,0.5,0.6,0.6,1c0.1,0.4,0.2,0.8,0.2,1.2
c0,0.5-0.1,0.9-0.2,1.3c-0.1,0.4-0.4,0.7-0.6,1c-0.3,0.3-0.6,0.5-1,0.6C34.9,41.9,34.5,42,34,42H31.7z M35.9,38.8
c0-0.3,0-0.6-0.1-0.8c-0.1-0.3-0.2-0.5-0.4-0.7c-0.2-0.2-0.4-0.3-0.6-0.4c-0.2-0.1-0.5-0.2-0.8-0.2h-1.1v4.2H34
c0.3,0,0.6-0.1,0.8-0.2c0.2-0.1,0.4-0.3,0.6-0.4c0.2-0.2,0.3-0.4,0.4-0.7C35.8,39.3,35.9,39.1,35.9,38.8z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8 KiB

View file

@ -20,9 +20,10 @@ Window {
objectName: "AddressBarDialog"
frame: HiddenFrame {}
hideBackground: true
visible: false
destroyOnInvisible: false
shown: false
destroyOnHidden: false
resizable: false
scale: 1.25 // Make this dialog a little larger than normal
@ -145,14 +146,14 @@ Window {
if (addressLine.text !== "") {
addressBarDialog.loadAddress(addressLine.text)
}
root.visible = false;
root.shown = false;
}
Keys.onPressed: {
switch (event.key) {
case Qt.Key_Escape:
case Qt.Key_Back:
root.visible = false
root.shown = false
event.accepted = true
break
case Qt.Key_Enter:

View file

@ -15,15 +15,15 @@ import Qt.labs.settings 1.0
import "styles-uit"
import "controls-uit" as HifiControls
import "windows-uit"
import "windows"
import "dialogs"
Window {
ScrollingWindow {
id: root
objectName: "AssetServer"
title: "Asset Browser"
resizable: true
destroyOnInvisible: true
destroyOnHidden: true
implicitWidth: 384; implicitHeight: 640
minSize: Qt.vector2d(200, 300)

View file

@ -2,22 +2,24 @@ import QtQuick 2.3
import QtQuick.Controls 1.2
import QtWebEngine 1.1
import "controls"
import "styles"
import "controls-uit"
import "styles-uit"
import "windows"
Window {
ScrollingWindow {
id: root
HifiConstants { id: hifi }
title: "Browser"
resizable: true
destroyOnInvisible: true
destroyOnHidden: true
width: 800
height: 600
property alias webView: webview
x: 100
y: 100
Component.onCompleted: {
visible = true
shown = true
addressBar.text = webview.url
}
@ -30,15 +32,9 @@ Window {
Item {
id:item
anchors.fill: parent
Rectangle {
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: webview.top
color: "white"
}
width: pane.contentWidth
implicitHeight: pane.scrollHeight
Row {
id: buttons
spacing: 4
@ -46,25 +42,37 @@ Window {
anchors.topMargin: 8
anchors.left: parent.left
anchors.leftMargin: 8
FontAwesome {
id: back; text: "\uf0a8"; size: 48; enabled: webview.canGoBack;
HiFiGlyphs {
id: back;
enabled: webview.canGoBack;
text: hifi.glyphs.backward
color: enabled ? hifi.colors.text : hifi.colors.disabledText
size: 48
MouseArea { anchors.fill: parent; onClicked: webview.goBack() }
}
FontAwesome {
id: forward; text: "\uf0a9"; size: 48; enabled: webview.canGoForward;
HiFiGlyphs {
id: forward;
enabled: webview.canGoForward;
text: hifi.glyphs.forward
color: enabled ? hifi.colors.text : hifi.colors.disabledText
MouseArea { anchors.fill: parent; onClicked: webview.goBack() }
size: 48
MouseArea { anchors.fill: parent; onClicked: webview.goForward() }
}
FontAwesome {
id: reload; size: 48; text: webview.loading ? "\uf057" : "\uf021"
MouseArea { anchors.fill: parent; onClicked: webview.loading ? webview.stop() : webview.reload() }
HiFiGlyphs {
id: reload;
enabled: webview.canGoForward;
text: webview.loading ? hifi.glyphs.close : hifi.glyphs.reload
color: enabled ? hifi.colors.text : hifi.colors.disabledText
size: 48
MouseArea { anchors.fill: parent; onClicked: webview.goForward() }
}
}
Border {
Item {
id: border
height: 48
radius: 8
anchors.top: parent.top
anchors.topMargin: 8
anchors.right: parent.right
@ -86,15 +94,18 @@ Window {
onSourceChanged: console.log("Icon url: " + source)
}
}
TextInput {
TextField {
id: addressBar
anchors.right: parent.right
anchors.rightMargin: 8
anchors.left: barIcon.right
anchors.leftMargin: 0
anchors.verticalCenter: parent.verticalCenter
focus: true
colorScheme: hifi.colorSchemes.dark
placeholderText: "Enter URL"
Component.onCompleted: scriptsModel.filterRegExp = new RegExp("^.*$", "i")
Keys.onPressed: {
switch(event.key) {
case Qt.Key_Enter:
@ -110,7 +121,7 @@ Window {
}
}
WebView {
WebEngineView {
id: webview
url: "http://highfidelity.com"
anchors.top: buttons.bottom
@ -119,7 +130,7 @@ Window {
anchors.left: parent.left
anchors.right: parent.right
onLoadingChanged: {
if (loadRequest.status == WebEngineView.LoadSucceededStatus) {
if (loadRequest.status === WebEngineView.LoadSucceededStatus) {
addressBar.text = loadRequest.url
}
}
@ -127,7 +138,7 @@ Window {
console.log("New icon: " + icon)
}
profile: desktop.browserProfile
//profile: desktop.browserProfile
}

View file

@ -12,9 +12,9 @@ import QtQuick 2.5
import Hifi 1.0 as Hifi
import "controls-uit"
import "windows-uit" as Windows
import "windows" as Windows
Windows.Window {
Windows.ScrollingWindow {
id: root
width: 800
height: 800

View file

@ -14,7 +14,7 @@ import "controls"
import "styles"
import "windows"
Window {
ScrollingWindow {
id: root
HifiConstants { id: hifi }
objectName: "LoginDialog"
@ -22,8 +22,9 @@ Window {
width: loginDialog.implicitWidth
// FIXME make movable
anchors.centerIn: parent
destroyOnInvisible: false
visible: false
destroyOnHidden: false
hideBackground: true
shown: false
LoginDialog {
id: loginDialog
@ -268,8 +269,8 @@ Window {
}
}
onVisibleChanged: {
if (!visible) {
onShownChanged: {
if (!shown) {
username.text = ""
password.text = ""
loginDialog.statusText = ""
@ -282,7 +283,7 @@ Window {
switch (event.key) {
case Qt.Key_Escape:
case Qt.Key_Back:
root.visible = false;
root.shown = false;
event.accepted = true;
break;

View file

@ -13,16 +13,16 @@ import QtQuick.Controls 1.4
import QtWebEngine 1.1
import QtWebChannel 1.0
import "windows-uit" as Windows
import "windows" as Windows
import "controls-uit" as Controls
import "styles-uit"
Windows.Window {
Windows.ScrollingWindow {
id: root
HifiConstants { id: hifi }
title: "WebWindow"
resizable: true
visible: false
shown: false
// Don't destroy on close... otherwise the JS/C++ will have a dangling pointer
destroyOnCloseButton: false
property alias source: webview.url

View file

@ -14,7 +14,7 @@ Windows.Window {
HifiConstants { id: hifi }
title: "QmlWindow"
resizable: true
visible: false
shown: false
focus: true
property var channel;
// Don't destroy on close... otherwise the JS/C++ will have a dangling pointer

View file

@ -15,18 +15,18 @@ import QtWebEngine 1.1
import QtWebChannel 1.0
import Qt.labs.settings 1.0
import "windows-uit"
import "windows"
import "controls-uit"
import "styles-uit"
Window {
ScrollingWindow {
id: toolWindow
resizable: true
objectName: "ToolWindow"
destroyOnCloseButton: false
destroyOnInvisible: false
destroyOnHidden: false
closable: true
visible: false
shown: false
title: "Edit"
property alias tabView: tabView
implicitWidth: 520; implicitHeight: 695
@ -142,7 +142,7 @@ Window {
return;
}
}
visible = false;
shown = false;
}
function findIndexForUrl(source) {
@ -172,7 +172,7 @@ Window {
var tab = tabView.getTab(index);
if (newVisible) {
toolWindow.visible = true
toolWindow.shown = true
tab.enabled = true
} else {
tab.enabled = false;

View file

@ -3,11 +3,11 @@ import QtQuick 2.3
import QtQuick.Controls 1.3
import QtQuick.Controls.Styles 1.3
import QtGraphicalEffects 1.0
import "controls"
import "styles"
import "controls-uit"
import "styles-uit"
import "windows"
Window {
ScrollingWindow {
id: root
HifiConstants { id: hifi }
objectName: "UpdateDialog"

View file

@ -15,7 +15,7 @@ import QtQuick.XmlListModel 2.0
import "../styles-uit"
import "../controls-uit" as HifiControls
import "../windows-uit"
import "../windows"
import "../hifi/models"
TableView {

View file

@ -52,6 +52,7 @@ FocusScope {
readonly property real menu: 8000
}
QtObject {
id: d
@ -64,7 +65,7 @@ FocusScope {
var oldChildren = expectedChildren;
var newChildren = d.getRepositionChildren();
if (oldRecommendedRect != Qt.rect(0,0,0,0)
if (oldRecommendedRect != Qt.rect(0,0,0,0) && oldRecommendedRect != Qt.rect(0,0,1,1)
&& (oldRecommendedRect != newRecommendedRect
|| oldChildren != newChildren)
) {
@ -93,6 +94,17 @@ FocusScope {
return item;
}
function findMatchingChildren(item, predicate) {
var results = [];
for (var i in item.children) {
var child = item.children[i];
if (predicate(child)) {
results.push(child);
}
}
return results;
}
function isTopLevelWindow(item) {
return item.topLevelWindow;
}
@ -106,19 +118,9 @@ FocusScope {
}
function getTopLevelWindows(predicate) {
var currentWindows = [];
if (!desktop) {
console.log("Could not find desktop for " + item)
return currentWindows;
}
for (var i = 0; i < desktop.children.length; ++i) {
var child = desktop.children[i];
if (isTopLevelWindow(child) && (!predicate || predicate(child))) {
currentWindows.push(child)
}
}
return currentWindows;
return findMatchingChildren(desktop, function(child) {
return (isTopLevelWindow(child) && (!predicate || predicate(child)));
});
}
function getDesktopWindow(item) {
@ -227,19 +229,9 @@ FocusScope {
}
function getRepositionChildren(predicate) {
var currentWindows = [];
if (!desktop) {
console.log("Could not find desktop");
return currentWindows;
}
for (var i = 0; i < desktop.children.length; ++i) {
var child = desktop.children[i];
if (child.shouldReposition === true && (!predicate || predicate(child))) {
currentWindows.push(child)
}
}
return currentWindows;
return findMatchingChildren(desktop, function(child) {
return (child.shouldReposition === true && (!predicate || predicate(child)));
});
}
function repositionAll() {
@ -265,6 +257,63 @@ FocusScope {
}
}
property bool pinned: false
property var hiddenChildren: []
function togglePinned() {
pinned = !pinned
}
function setPinned(newPinned) {
pinned = newPinned
}
property real unpinnedAlpha: 1.0;
Behavior on unpinnedAlpha {
NumberAnimation {
easing.type: Easing.Linear;
duration: 300
}
}
state: "NORMAL"
states: [
State {
name: "NORMAL"
PropertyChanges { target: desktop; unpinnedAlpha: 1.0 }
},
State {
name: "PINNED"
PropertyChanges { target: desktop; unpinnedAlpha: 0.0 }
}
]
transitions: [
Transition {
NumberAnimation { properties: "unpinnedAlpha"; duration: 300 }
}
]
onPinnedChanged: {
if (pinned) {
nullFocus.focus = true;
nullFocus.forceActiveFocus();
// recalculate our non-pinned children
hiddenChildren = d.findMatchingChildren(desktop, function(child){
return !d.isTopLevelWindow(child) && child.visible && !child.pinned;
});
hiddenChildren.forEach(function(child){
child.opacity = Qt.binding(function(){ return desktop.unpinnedAlpha });
});
}
state = pinned ? "PINNED" : "NORMAL"
}
onShowDesktop: pinned = false
function raise(item) {
var targetWindow = d.getDesktopWindow(item);
if (!targetWindow) {
@ -422,7 +471,6 @@ FocusScope {
event.accepted = false;
}
function unfocusWindows() {
var windows = d.getTopLevelWindows();
for (var i = 0; i < windows.length; ++i) {
@ -433,6 +481,8 @@ FocusScope {
FocusHack { id: focusHack; }
FocusScope { id: nullFocus; }
Rectangle {
id: focusDebugger;
objectName: "focusDebugger"

View file

@ -18,7 +18,7 @@ import QtQuick.Dialogs 1.2 as OriginalDialogs
import ".."
import "../controls-uit"
import "../styles-uit"
import "../windows-uit"
import "../windows"
import "fileDialog"
@ -729,7 +729,7 @@ ModalWindow {
Action {
id: cancelAction
text: "Cancel"
onTriggered: { canceled(); root.visible = false; }
onTriggered: { canceled(); root.shown = false; }
}
}

View file

@ -14,7 +14,7 @@ import QtQuick.Dialogs 1.2 as OriginalDialogs
import "../controls-uit"
import "../styles-uit"
import "../windows-uit"
import "../windows"
import "messageDialog"
@ -24,7 +24,7 @@ ModalWindow {
implicitWidth: 640
implicitHeight: 320
destroyOnCloseButton: true
destroyOnInvisible: true
destroyOnHidden: true
visible: true
signal selected(int button);

View file

@ -13,14 +13,14 @@ import QtQuick.Controls 1.4
import "../controls-uit" as HifiControls
import "../styles-uit"
import "../windows-uit"
import "../windows"
import "preferences"
Window {
ScrollingWindow {
id: root
title: "Preferences"
resizable: true
destroyOnInvisible: true
destroyOnHidden: true
width: 500
height: 577
property var sections: []

View file

@ -14,7 +14,7 @@ import QtQuick.Dialogs 1.2 as OriginalDialogs
import "../controls-uit"
import "../styles-uit"
import "../windows-uit"
import "../windows"
ModalWindow {
id: root

View file

@ -12,7 +12,7 @@ import QtQuick 2.5
import QtQuick.Controls 1.4
import QtWebEngine 1.1
import "../../windows-uit" as Windows
import "../../windows" as Windows
import "../../controls-uit" as Controls
import "../../styles-uit"
@ -23,15 +23,10 @@ Windows.Window {
resizable: true
modality: Qt.ApplicationModal
Item {
width: pane.contentWidth
implicitHeight: pane.scrollHeight
Controls.WebView {
id: webview
anchors.fill: parent
url: "https://metaverse.highfidelity.com/marketplace?category=avatars"
focus: true
}
Controls.WebView {
id: webview
anchors.fill: parent
url: "https://metaverse.highfidelity.com/marketplace?category=avatars"
focus: true
}
}

View file

@ -1,14 +1,18 @@
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtWebEngine 1.1;
import Qt.labs.settings 1.0
import "../desktop"
import "../desktop" as OriginalDesktop
import ".."
import "."
import "./toolbars"
Desktop {
OriginalDesktop.Desktop {
id: desktop
MouseArea {
id: hoverWatch
anchors.fill: parent
hoverEnabled: true
propagateComposedEvents: true
@ -18,13 +22,6 @@ Desktop {
acceptedButtons: Qt.NoButton
}
Component.onCompleted: {
WebEngine.settings.javascriptCanOpenWindows = true;
WebEngine.settings.javascriptCanAccessClipboard = false;
WebEngine.settings.spatialNavigationEnabled = false;
WebEngine.settings.localContentCanAccessRemoteUrls = true;
}
// The tool window, one instance
property alias toolWindow: toolWindow
ToolWindow { id: toolWindow }
@ -47,7 +44,42 @@ Desktop {
}
}
property var toolbars: ({})
Component { id: toolbarBuilder; Toolbar { } }
Component.onCompleted: {
WebEngine.settings.javascriptCanOpenWindows = true;
WebEngine.settings.javascriptCanAccessClipboard = false;
WebEngine.settings.spatialNavigationEnabled = false;
WebEngine.settings.localContentCanAccessRemoteUrls = true;
var sysToolbar = desktop.getToolbar("com.highfidelity.interface.toolbar.system");
var toggleHudButton = sysToolbar.addButton({
objectName: "hudToggle",
imageURL: "../../../icons/hud-01.svg",
visible: true,
pinned: true,
});
toggleHudButton.yOffset = Qt.binding(function(){
return desktop.pinned ? 50 : 0
});
toggleHudButton.clicked.connect(function(){
console.log("Clicked on hud button")
var overlayMenuItem = "Overlays"
MenuInterface.setIsOptionChecked(overlayMenuItem, !MenuInterface.isOptionChecked(overlayMenuItem));
});
}
// Create or fetch a toolbar with the given name
function getToolbar(name) {
var result = toolbars[name];
if (!result) {
result = toolbars[name] = toolbarBuilder.createObject(desktop, {});
result.objectName = name;
}
return result;
}
}

View file

@ -6,17 +6,17 @@ import QtQuick.Controls.Styles 1.4
import "../../styles-uit"
import "../../controls-uit" as HifiControls
import "../../windows-uit"
import "../../windows"
import "attachments"
Window {
ScrollingWindow {
id: root
title: "Attachments"
objectName: "AttachmentsDialog"
width: 600
height: 600
resizable: true
destroyOnInvisible: true
destroyOnHidden: true
minSize: Qt.vector2d(400, 500)
HifiConstants { id: hifi }

View file

@ -9,9 +9,9 @@ import "../models"
import "../../styles-uit"
import "../../controls-uit" as HifiControls
import "../../windows-uit"
import "../../windows"
Window {
ScrollingWindow {
id: root
resizable: true
width: 600

View file

@ -15,14 +15,14 @@ import Qt.labs.settings 1.0
import "../../styles-uit"
import "../../controls-uit" as HifiControls
import "../../windows-uit"
import "../../windows"
Window {
ScrollingWindow {
id: root
objectName: "RunningScripts"
title: "Running Scripts"
resizable: true
destroyOnInvisible: true
destroyOnHidden: true
implicitWidth: 424
implicitHeight: isHMD ? 695 : 728
minSize: Qt.vector2d(424, 300)
@ -34,6 +34,9 @@ Window {
property var runningScriptsModel: ListModel { }
property bool isHMD: false
onVisibleChanged: console.log("Running scripts visible changed to " + visible)
onShownChanged: console.log("Running scripts visible changed to " + visible)
Settings {
category: "Overlay.RunningScripts"
property alias x: root.x

View file

@ -7,7 +7,7 @@ import "../../windows"
import "../../js/Utils.js" as Utils
import "../models"
Window {
ScrollingWindow {
id: root
resizable: true
width: 516

View file

@ -8,7 +8,7 @@ import "."
import ".."
import "../../../styles-uit"
import "../../../controls-uit" as HifiControls
import "../../../windows-uit"
import "../../../windows"
Item {
height: column.height + 2 * 8

View file

@ -3,7 +3,7 @@ import QtQuick.Controls 1.4
import "../../../styles-uit"
import "../../../controls-uit" as HifiControls
import "../../../windows-uit"
import "../../../windows"
Item {
id: root

View file

@ -0,0 +1,151 @@
import QtQuick 2.5
import QtQuick.Controls 1.4
import Qt.labs.settings 1.0
import "../../windows"
import "."
Window {
id: window
frame: ToolFrame {
horizontalSpacers: horizontal
verticalSpacers: !horizontal
}
hideBackground: true
resizable: false
destroyOnCloseButton: false
destroyOnHidden: false
closable: false
shown: true
width: content.width
height: content.height
visible: true
// Disable this window from being able to call 'desktop.raise() and desktop.showDesktop'
activator: Item {}
property bool horizontal: true
property real buttonSize: 50;
property var buttons: []
property var container: horizontal ? row : column
Settings {
category: "toolbar/" + window.objectName
property alias x: window.x
property alias y: window.y
}
onHorizontalChanged: {
var newParent = horizontal ? row : column;
for (var i in buttons) {
var child = buttons[i];
child.parent = newParent;
if (horizontal) {
child.y = 0
} else {
child.x = 0
}
}
}
Item {
id: content
implicitHeight: horizontal ? row.height : column.height
implicitWidth: horizontal ? row.width : column.width
Row {
id: row
spacing: 6
}
Column {
id: column
spacing: 6
}
Component { id: toolbarButtonBuilder; ToolbarButton { } }
Connections {
target: desktop
onPinnedChanged: {
if (!window.pinned) {
return;
}
var newPinned = desktop.pinned;
for (var i in buttons) {
var child = buttons[i];
if (desktop.pinned) {
if (!child.pinned) {
child.visible = false;
}
} else {
child.visible = true;
}
}
}
}
}
function findButtonIndex(name) {
if (!name) {
return -1;
}
for (var i in buttons) {
var child = buttons[i];
if (child.objectName === name) {
return i;
}
}
return -1;
}
function findButton(name) {
var index = findButtonIndex(name);
if (index < 0) {
return;
}
return buttons[index];
}
function addButton(properties) {
properties = properties || {}
// If a name is specified, then check if there's an existing button with that name
// and return it if so. This will allow multiple clients to listen to a single button,
// and allow scripts to be idempotent so they don't duplicate buttons if they're reloaded
var result = findButton(properties.objectName);
if (result) {
return result;
}
properties.toolbar = this;
properties.opacity = 0;
result = toolbarButtonBuilder.createObject(container, properties);
buttons.push(result);
result.opacity = 1;
updatePinned();
return result;
}
function removeButton(name) {
var index = findButtonIndex(name);
if (index < -1) {
console.warn("Tried to remove non-existent button " + name);
return;
}
buttons[index].destroy();
buttons.splice(index, 1);
updatePinned();
}
function updatePinned() {
var newPinned = false;
for (var i in buttons) {
var child = buttons[i];
if (child.pinned) {
newPinned = true;
break;
}
}
pinned = newPinned;
}
}

View file

@ -0,0 +1,65 @@
import QtQuick 2.5
import QtQuick.Controls 1.4
Item {
id: button
property alias imageURL: image.source
property alias alpha: button.opacity
property var subImage;
property int yOffset: 0
property int buttonState: 0
property var toolbar;
property real size: 50 // toolbar ? toolbar.buttonSize : 50
width: size; height: size
property bool pinned: false
clip: true
Behavior on opacity {
NumberAnimation {
duration: 150
easing.type: Easing.InOutCubic
}
}
property alias fadeTargetProperty: button.opacity
onFadeTargetPropertyChanged: {
visible = (fadeTargetProperty !== 0.0);
}
onVisibleChanged: {
if ((!visible && fadeTargetProperty != 0.0) || (visible && fadeTargetProperty == 0.0)) {
var target = visible;
visible = !visible;
fadeTargetProperty = target ? 1.0 : 0.0;
return;
}
}
onButtonStateChanged: {
yOffset = size * buttonState
}
Component.onCompleted: {
if (subImage) {
if (subImage.y) {
yOffset = subImage.y;
}
}
}
signal clicked()
Image {
id: image
y: -button.yOffset;
width: parent.width
}
MouseArea {
anchors.fill: parent
onClicked: button.clicked();
}
}

View file

@ -1,119 +0,0 @@
//
// DefaultFrame.qml
//
// Created by Bradley Austin Davis on 12 Jan 2016
// Copyright 2016 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 QtQuick 2.5
import QtGraphicalEffects 1.0
import "."
import "../styles-uit"
Frame {
HifiConstants { id: hifi }
Rectangle {
// Dialog frame
id: frameContent
readonly property int iconSize: hifi.dimensions.frameIconSize
readonly property int frameMargin: 9
readonly property int frameMarginLeft: frameMargin
readonly property int frameMarginRight: frameMargin
readonly property int frameMarginTop: 2 * frameMargin + iconSize
readonly property int frameMarginBottom: iconSize + 11
anchors {
topMargin: -frameMarginTop
leftMargin: -frameMarginLeft
rightMargin: -frameMarginRight
bottomMargin: -frameMarginBottom
}
anchors.fill: parent
color: hifi.colors.baseGrayHighlight40
border {
width: hifi.dimensions.borderWidth
color: hifi.colors.faintGray50
}
radius: hifi.dimensions.borderRadius
// Enable dragging of the window
MouseArea {
anchors.fill: parent
drag.target: window
}
Row {
id: controlsRow
anchors {
right: parent.right;
top: parent.top;
topMargin: frameContent.frameMargin + 1 // Move down a little to visually align with the title
rightMargin: frameContent.frameMarginRight;
}
spacing: frameContent.iconSize / 4
HiFiGlyphs {
// "Pin" button
visible: false
text: (frame.pinned && !pinClickArea.containsMouse) || (!frame.pinned && pinClickArea.containsMouse) ? hifi.glyphs.pinInverted : hifi.glyphs.pin
color: pinClickArea.containsMouse && !pinClickArea.pressed ? hifi.colors.redHighlight : hifi.colors.white
size: frameContent.iconSize
MouseArea {
id: pinClickArea
anchors.fill: parent
hoverEnabled: true
propagateComposedEvents: true
onClicked: { frame.pin(); mouse.accepted = false; }
}
}
HiFiGlyphs {
// "Close" button
visible: window ? window.closable : false
text: closeClickArea.containsPress ? hifi.glyphs.closeInverted : hifi.glyphs.close
color: closeClickArea.containsMouse ? hifi.colors.redHighlight : hifi.colors.white
size: frameContent.iconSize
MouseArea {
id: closeClickArea
anchors.fill: parent
hoverEnabled: true
onClicked: window.visible = false;
}
}
}
RalewayRegular {
// Title
id: titleText
anchors {
left: parent.left
leftMargin: frameContent.frameMarginLeft + hifi.dimensions.contentMargin.x
right: controlsRow.left
rightMargin: frameContent.iconSize
top: parent.top
topMargin: frameContent.frameMargin
}
text: window ? window.title : ""
color: hifi.colors.white
size: hifi.fontSizes.overlayTitle
}
DropShadow {
source: titleText
anchors.fill: titleText
horizontalOffset: 2
verticalOffset: 2
samples: 2
color: hifi.colors.baseGrayShadow60
visible: (window && window.focus)
cached: true
}
}
}

View file

@ -1,60 +0,0 @@
//
// Fadable.qml
//
// Created by Bradley Austin Davis on 15 Jan 2016
// Copyright 2016 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 QtQuick 2.5
import QtQuick.Controls 1.4
import QtGraphicalEffects 1.0
import "../styles-uit"
// Enable window visibility transitions
FocusScope {
id: root
HifiConstants { id: hifi }
Component.onCompleted: {
fadeTargetProperty = visible ? 1.0 : 0.0
}
// The target property to animate, usually scale or opacity
property alias fadeTargetProperty: root.opacity
// always start the property at 0 to enable fade in on creation
fadeTargetProperty: 0
// DO NOT set visible to false or when derived types override it it
// will short circuit the fade in on initial visibility
// visible: false <--- NO
// Some dialogs should be destroyed when they become
// invisible, so handle that
onVisibleChanged: {
// If someone directly set the visibility to false
// toggle it back on and use the targetVisible flag to transition
// via fading.
if ((!visible && fadeTargetProperty != 0.0) || (visible && fadeTargetProperty == 0.0)) {
var target = visible;
visible = !visible;
fadeTargetProperty = target ? 1.0 : 0.0;
return;
}
}
// The actual animator
Behavior on fadeTargetProperty {
NumberAnimation {
duration: hifi.effects.fadeInDuration
easing.type: Easing.InOutCubic
}
}
// Once we're transparent, disable the dialog's visibility
onFadeTargetPropertyChanged: {
visible = (fadeTargetProperty != 0.0);
}
}

View file

@ -1,133 +0,0 @@
//
// Frame.qml
//
// Created by Bradley Austin Davis on 12 Jan 2016
// Copyright 2016 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 QtQuick 2.5
import QtGraphicalEffects 1.0
import "../styles-uit"
import "../js/Utils.js" as Utils
Item {
id: frame
HifiConstants { id: hifi }
default property var decoration
property bool gradientsSupported: desktop.gradientsSupported
readonly property int frameMarginLeft: frameContent.frameMarginLeft
readonly property int frameMarginRight: frameContent.frameMarginRight
readonly property int frameMarginTop: frameContent.frameMarginTop
readonly property int frameMarginBottom: frameContent.frameMarginBottom
// Frames always fill their parents, but their decorations may extend
// beyond the window via negative margin sizes
anchors.fill: parent
children: [
focusShadow,
decoration,
sizeOutline,
debugZ,
sizeDrag
]
Text {
id: debugZ
visible: DebugQML
text: window ? "Z: " + window.z : ""
y: window ? window.height + 4 : 0
}
function deltaSize(dx, dy) {
var newSize = Qt.vector2d(window.width + dx, window.height + dy);
newSize = Utils.clampVector(newSize, window.minSize, window.maxSize);
window.width = newSize.x
window.height = newSize.y
}
RadialGradient {
id: focusShadow
width: 1.66 * window.width
height: 1.66 * window.height
x: (window.width - width) / 2
y: window.height / 2 - 0.375 * height
visible: gradientsSupported && window && window.focus && pane.visible
gradient: Gradient {
// GradientStop position 0.5 is at full circumference of circle that fits inside the square.
GradientStop { position: 0.0; color: "#ff000000" } // black, 100% opacity
GradientStop { position: 0.333; color: "#1f000000" } // black, 12% opacity
GradientStop { position: 0.5; color: "#00000000" } // black, 0% opacity
GradientStop { position: 1.0; color: "#00000000" }
}
cached: true
}
Rectangle {
id: sizeOutline
x: -frameMarginLeft
y: -frameMarginTop
width: window ? window.width + frameMarginLeft + frameMarginRight + 2 : 0
height: window ? window.height + frameMarginTop + frameMarginBottom + 2 : 0
color: hifi.colors.baseGrayHighlight15
border.width: 3
border.color: hifi.colors.white50
radius: hifi.dimensions.borderRadius
visible: window ? !pane.visible : false
}
MouseArea {
// Resize handle
id: sizeDrag
width: hifi.dimensions.frameIconSize
height: hifi.dimensions.frameIconSize
enabled: window ? window.resizable : false
hoverEnabled: true
x: window ? window.width + frameMarginRight - hifi.dimensions.frameIconSize : 0
y: window ? window.height + 4 : 0
property vector2d pressOrigin
property vector2d sizeOrigin
property bool hid: false
onPressed: {
//console.log("Pressed on size")
pressOrigin = Qt.vector2d(mouseX, mouseY)
sizeOrigin = Qt.vector2d(window.content.width, window.content.height)
hid = false;
}
onReleased: {
if (hid) {
pane.visible = true
frameContent.visible = true
hid = false;
}
}
onPositionChanged: {
if (pressed) {
if (pane.visible) {
pane.visible = false;
frameContent.visible = false
hid = true;
}
var delta = Qt.vector2d(mouseX, mouseY).minus(pressOrigin);
frame.deltaSize(delta.x, delta.y)
}
}
HiFiGlyphs {
visible: sizeDrag.enabled
x: -11 // Move a little to visually align
y: window.modality == Qt.ApplicationModal ? -6 : -4
text: hifi.glyphs.resizeHandle
size: hifi.dimensions.frameIconSize + 10
color: sizeDrag.containsMouse || sizeDrag.pressed
? hifi.colors.white
: (window.colorScheme == hifi.colorSchemes.dark ? hifi.colors.white50 : hifi.colors.lightGrayText80)
}
}
}

View file

@ -1,98 +0,0 @@
//
// ModalFrame.qml
//
// Created by Bradley Austin Davis on 15 Jan 2016
// Copyright 2015 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 QtQuick 2.5
import "."
import "../controls-uit"
import "../styles-uit"
Frame {
HifiConstants { id: hifi }
Rectangle {
id: frameContent
readonly property bool hasTitle: window.title != ""
readonly property int frameMarginLeft: hifi.dimensions.modalDialogMargin.x
readonly property int frameMarginRight: hifi.dimensions.modalDialogMargin.x
readonly property int frameMarginTop: hifi.dimensions.modalDialogMargin.y + (frameContent.hasTitle ? hifi.dimensions.modalDialogTitleHeight + 10 : 0)
readonly property int frameMarginBottom: hifi.dimensions.modalDialogMargin.y
signal frameClicked();
anchors {
fill: parent
topMargin: -frameMarginTop
leftMargin: -frameMarginLeft
rightMargin: -frameMarginRight
bottomMargin: -frameMarginBottom
}
border {
width: hifi.dimensions.borderWidth
color: hifi.colors.lightGrayText80
}
radius: hifi.dimensions.borderRadius
color: hifi.colors.faintGray
// Enable dragging of the window
MouseArea {
anchors.fill: parent
drag.target: window
enabled: window.draggable
onClicked: window.frameClicked();
}
Item {
visible: frameContent.hasTitle
anchors.fill: parent
anchors {
topMargin: -parent.anchors.topMargin
leftMargin: -parent.anchors.leftMargin
rightMargin: -parent.anchors.rightMargin
}
Item {
width: title.width + (icon.text !== "" ? icon.width + hifi.dimensions.contentSpacing.x : 0)
x: (parent.width - width) / 2
onWidthChanged: window.titleWidth = width
HiFiGlyphs {
id: icon
text: window.iconText ? window.iconText : ""
size: window.iconSize ? window.iconSize : 30
color: hifi.colors.lightGray
visible: text != ""
anchors.verticalCenter: title.verticalCenter
anchors.left: parent.left
}
RalewayRegular {
id: title
text: window.title
elide: Text.ElideRight
color: hifi.colors.baseGrayHighlight
size: hifi.fontSizes.overlayTitle
y: -hifi.dimensions.modalDialogTitleHeight
anchors.right: parent.right
}
}
Rectangle {
anchors.left: parent.left
anchors.right: parent.right
height: 1
color: hifi.colors.lightGray
}
}
}
}

View file

@ -1,28 +0,0 @@
//
// ModalWindow.qml
//
// Created by Bradley Austin Davis on 22 Jan 2016
// Copyright 2015 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 QtQuick 2.5
import "."
Window {
id: window
modality: Qt.ApplicationModal
destroyOnCloseButton: true
destroyOnInvisible: true
frame: ModalFrame { }
property int colorScheme: hifi.colorSchemes.light
property bool draggable: false
signal frameClicked();
anchors.centerIn: draggable ? undefined : parent
}

View file

@ -1,343 +0,0 @@
//
// Window.qml
//
// Created by Bradley Austin Davis on 12 Jan 2016
// Copyright 2016 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 QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import QtGraphicalEffects 1.0
import "."
import "../styles-uit"
// FIXME how do I set the initial position of a window without
// overriding places where the a individual client of the window
// might be setting the position with a Settings{} element?
// FIXME how to I enable dragging without allowing the window to lay outside
// of the desktop? How do I ensure when the desktop resizes all the windows
// are still at least partially visible?
Fadable {
id: window
HifiConstants { id: hifi }
// The Window size is the size of the content, while the frame
// decorations can extend outside it.
implicitHeight: content ? content.height : 0
implicitWidth: content ? content.width : 0
x: desktop.invalid_position; y: desktop.invalid_position;
enabled: visible
signal windowDestroyed();
property int modality: Qt.NonModal
readonly property bool topLevelWindow: true
property string title
// Should the window be closable control?
property bool closable: true
// Should the window try to remain on top of other windows?
property bool alwaysOnTop: false
// Should hitting the close button hide or destroy the window?
property bool destroyOnCloseButton: true
// Should hiding the window destroy it or just hide it?
property bool destroyOnInvisible: false
// FIXME support for pinned / unpinned pending full design
// property bool pinnable: false
// property bool pinned: false
property bool resizable: false
property bool gradientsSupported: desktop.gradientsSupported
property int colorScheme: hifi.colorSchemes.dark
property vector2d minSize: Qt.vector2d(100, 100)
property vector2d maxSize: Qt.vector2d(1280, 800)
// The content to place inside the window, determined by the client
default property var content
property var footer: Item { } // Optional static footer at the bottom of the dialog.
function setDefaultFocus() {} // Default function; can be overridden by dialogs.
property var rectifier: Timer {
property bool executing: false;
interval: 100
repeat: false
running: false
onTriggered: {
executing = true;
x = Math.floor(x);
y = Math.floor(y);
executing = false;
}
function begin() {
if (!executing) {
restart();
}
}
}
onXChanged: rectifier.begin();
onYChanged: rectifier.begin();
// This mouse area serves to raise the window. To function, it must live
// in the window and have a higher Z-order than the content, but follow
// the position and size of frame decoration
property var activator: MouseArea {
width: frame.decoration.width
height: frame.decoration.height
x: frame.decoration.anchors.leftMargin
y: frame.decoration.anchors.topMargin
propagateComposedEvents: true
acceptedButtons: Qt.AllButtons
enabled: window.visible
onPressed: {
//console.log("Pressed on activator area");
window.raise();
mouse.accepted = false;
}
}
// This mouse area serves to swallow mouse events while the mouse is over the window
// to prevent things like mouse wheel events from reaching the application and changing
// the camera if the user is scrolling through a list and gets to the end.
property var swallower: MouseArea {
width: frame.decoration.width
height: frame.decoration.height
x: frame.decoration.anchors.leftMargin
y: frame.decoration.anchors.topMargin
hoverEnabled: true
acceptedButtons: Qt.AllButtons
enabled: window.visible
onClicked: {}
onDoubleClicked: {}
onPressAndHold: {}
onReleased: {}
onWheel: {}
}
// Default to a standard frame. Can be overriden to provide custom
// frame styles, like a full desktop frame to simulate a modal window
property var frame: DefaultFrame { }
// Scrollable window content.
property var pane: Item {
property bool isScrolling: scrollView.height < scrollView.contentItem.height
property int contentWidth: scrollView.width - (isScrolling ? 10 : 0)
property int scrollHeight: scrollView.height
anchors.fill: parent
anchors.rightMargin: isScrolling ? 11 : 0
Rectangle {
id: contentBackground
anchors.fill: parent
anchors.rightMargin: parent.isScrolling ? 11 : 0
color: hifi.colors.baseGray
visible: modality != Qt.ApplicationModal
}
LinearGradient {
visible: gradientsSupported && modality != Qt.ApplicationModal
anchors.top: contentBackground.bottom
anchors.left: contentBackground.left
width: contentBackground.width - 1
height: 4
start: Qt.point(0, 0)
end: Qt.point(0, 4)
gradient: Gradient {
GradientStop { position: 0.0; color: hifi.colors.darkGray }
GradientStop { position: 1.0; color: hifi.colors.darkGray0 }
}
cached: true
}
ScrollView {
id: scrollView
contentItem: content
horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff
verticalScrollBarPolicy: Qt.ScrollBarAsNeeded
anchors.fill: parent
anchors.rightMargin: parent.isScrolling ? 1 : 0
anchors.bottomMargin: footer.height > 0 ? footerPane.height : 0
style: ScrollViewStyle {
padding.right: -7 // Move to right away from content.
handle: Item {
implicitWidth: 8
Rectangle {
radius: 4
color: hifi.colors.white30
anchors {
fill: parent
leftMargin: 2 // Finesse size and position.
topMargin: 1
bottomMargin: 1
}
}
}
scrollBarBackground: Item {
implicitWidth: 10
Rectangle {
color: hifi.colors.darkGray30
radius: 4
anchors {
fill: parent
topMargin: -1 // Finesse size
bottomMargin: -2
}
}
}
incrementControl: Item {
visible: false
}
decrementControl: Item {
visible: false
}
}
}
Rectangle {
// Optional non-scrolling footer.
id: footerPane
anchors {
left: parent.left
bottom: parent.bottom
}
width: parent.contentWidth
height: footer.height + 2 * hifi.dimensions.contentSpacing.y + 3
color: hifi.colors.baseGray
visible: footer.height > 0
Item {
// Horizontal rule.
anchors.fill: parent
Rectangle {
width: parent.width
height: 1
y: 1 // Stop displaying content just above horizontal rule/=.
color: hifi.colors.baseGrayShadow
}
Rectangle {
width: parent.width
height: 1
y: 2
color: hifi.colors.baseGrayHighlight
}
}
Item {
anchors.fill: parent
anchors.topMargin: 3 // Horizontal rule.
children: [ footer ]
}
}
}
children: [ swallower, frame, pane, activator ]
Component.onCompleted: {
window.parentChanged.connect(raise);
raise();
setDefaultFocus();
centerOrReposition();
}
Component.onDestruction: {
window.parentChanged.disconnect(raise); // Prevent warning on shutdown
windowDestroyed();
}
onVisibleChanged: {
if (!visible && destroyOnInvisible) {
destroy();
return;
}
if (visible) {
raise();
}
enabled = visible
if (visible && parent) {
centerOrReposition();
}
}
function centerOrReposition() {
if (x == desktop.invalid_position && y == desktop.invalid_position) {
desktop.centerOnVisible(window);
} else {
desktop.repositionOnVisible(window);
}
}
function raise() {
if (visible && parent) {
desktop.raise(window)
}
}
function pin() {
// pinned = ! pinned
}
// our close function performs the same way as the OffscreenUI class:
// don't do anything but manipulate the targetVisible flag and let the other
// mechanisms decide if the window should be destroyed after the close
// animation completes
// FIXME using this close function messes up the visibility signals received by the
// type and it's derived types
// function close() {
// console.log("Closing " + window)
// if (destroyOnCloseButton) {
// destroyOnInvisible = true
// }
// visible = false;
// }
function framedRect() {
if (!frame || !frame.decoration) {
return Qt.rect(0, 0, window.width, window.height)
}
return Qt.rect(frame.decoration.anchors.leftMargin, frame.decoration.anchors.topMargin,
window.width - frame.decoration.anchors.leftMargin - frame.decoration.anchors.rightMargin,
window.height - frame.decoration.anchors.topMargin - frame.decoration.anchors.bottomMargin)
}
Keys.onPressed: {
switch(event.key) {
case Qt.Key_Control:
case Qt.Key_Shift:
case Qt.Key_Meta:
case Qt.Key_Alt:
break;
case Qt.Key_W:
if (window.closable && (event.modifiers === Qt.ControlModifier)) {
visible = false
event.accepted = true
}
// fall through
default:
// Consume unmodified keyboard entries while the window is focused, to prevent them
// from propagating to the application
if (event.modifiers === Qt.NoModifier) {
event.accepted = true;
}
break;
}
}
}

View file

@ -1,20 +1,48 @@
//
// DefaultFrame.qml
//
// Created by Bradley Austin Davis on 12 Jan 2016
// Copyright 2016 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 QtQuick 2.5
import QtGraphicalEffects 1.0
import "."
import "../controls"
import "../styles-uit"
Frame {
id: frame
property bool wideTopMargin: (window && (window.closable || window.title));
HifiConstants { id: hifi }
Rectangle {
anchors { margins: -iconSize; topMargin: -iconSize * (wideTopMargin ? 2 : 1); }
anchors.fill: parent;
color: "#7f7f7f7f";
radius: 3;
// Dialog frame
id: frameContent
// Allow dragging of the window
readonly property int iconSize: hifi.dimensions.frameIconSize
readonly property int frameMargin: 9
readonly property int frameMarginLeft: frameMargin
readonly property int frameMarginRight: frameMargin
readonly property int frameMarginTop: 2 * frameMargin + iconSize
readonly property int frameMarginBottom: iconSize + 11
anchors {
topMargin: -frameMarginTop
leftMargin: -frameMarginLeft
rightMargin: -frameMarginRight
bottomMargin: -frameMarginBottom
}
anchors.fill: parent
color: hifi.colors.baseGrayHighlight40
border {
width: hifi.dimensions.borderWidth
color: hifi.colors.faintGray50
}
radius: hifi.dimensions.borderRadius
// Enable dragging of the window
MouseArea {
anchors.fill: parent
drag.target: window
@ -22,48 +50,70 @@ Frame {
Row {
id: controlsRow
anchors { right: parent.right; top: parent.top; rightMargin: iconSize; topMargin: iconSize / 2; }
spacing: iconSize / 4
FontAwesome {
visible: false
text: "\uf08d"
style: Text.Outline; styleColor: "white"
size: frame.iconSize
rotation: !frame.parent ? 90 : frame.parent.pinned ? 0 : 90
color: frame.pinned ? "red" : "black"
anchors {
right: parent.right;
top: parent.top;
topMargin: frameContent.frameMargin + 1 // Move down a little to visually align with the title
rightMargin: frameContent.frameMarginRight;
}
spacing: frameContent.iconSize / 4
HiFiGlyphs {
// "Pin" button
visible: window.pinnable
text: window.pinned ? hifi.glyphs.pinInverted : hifi.glyphs.pin
color: pinClickArea.pressed ? hifi.colors.redHighlight : hifi.colors.white
size: frameContent.iconSize
MouseArea {
id: pinClickArea
anchors.fill: parent
hoverEnabled: true
propagateComposedEvents: true
onClicked: { frame.pin(); mouse.accepted = false; }
onClicked: window.pinned = !window.pinned;
}
}
FontAwesome {
HiFiGlyphs {
// "Close" button
visible: window ? window.closable : false
text: closeClickArea.containsMouse ? "\uf057" : "\uf05c"
style: Text.Outline;
styleColor: "white"
color: closeClickArea.containsMouse ? "red" : "black"
size: frame.iconSize
text: closeClickArea.containsPress ? hifi.glyphs.closeInverted : hifi.glyphs.close
color: closeClickArea.containsMouse ? hifi.colors.redHighlight : hifi.colors.white
size: frameContent.iconSize
MouseArea {
id: closeClickArea
anchors.fill: parent
hoverEnabled: true
onClicked: window.visible = false;
onClicked: window.shown = false;
}
}
}
Text {
RalewayRegular {
// Title
id: titleText
anchors { left: parent.left; leftMargin: iconSize; right: controlsRow.left; rightMargin: iconSize; top: parent.top; topMargin: iconSize / 2; }
anchors {
left: parent.left
leftMargin: frameContent.frameMarginLeft + hifi.dimensions.contentMargin.x
right: controlsRow.left
rightMargin: frameContent.iconSize
top: parent.top
topMargin: frameContent.frameMargin
}
text: window ? window.title : ""
elide: Text.ElideRight
font.bold: true
color: (window && window.focus) ? "white" : "gray"
style: Text.Outline;
styleColor: "black"
color: hifi.colors.white
size: hifi.fontSizes.overlayTitle
}
DropShadow {
source: titleText
anchors.fill: titleText
horizontalOffset: 2
verticalOffset: 2
samples: 2
color: hifi.colors.baseGrayShadow60
visible: (window && window.focus)
cached: true
}
}
}

View file

@ -1,8 +1,18 @@
//
// Fadable.qml
//
// Created by Bradley Austin Davis on 15 Jan 2016
// Copyright 2016 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 QtQuick 2.5
import QtQuick.Controls 1.4
import QtGraphicalEffects 1.0
import "."
import "../styles"
import "../styles-uit"
// Enable window visibility transitions
FocusScope {
@ -13,6 +23,7 @@ FocusScope {
fadeTargetProperty = visible ? 1.0 : 0.0
}
property var completionCallback;
// The target property to animate, usually scale or opacity
property alias fadeTargetProperty: root.opacity
// always start the property at 0 to enable fade in on creation
@ -33,6 +44,13 @@ FocusScope {
fadeTargetProperty = target ? 1.0 : 0.0;
return;
}
// Now handle completions
if (completionCallback) {
completionCallback();
completionCallback = undefined;
}
}
// The actual animator
@ -43,8 +61,17 @@ FocusScope {
}
}
// Once we're transparent, disable the dialog's visibility
onFadeTargetPropertyChanged: {
visible = (fadeTargetProperty != 0.0);
}
function fadeIn(callback) {
completionCallback = callback;
fadeTargetProperty = 1.0;
}
function fadeOut(callback) {
completionCallback = callback;
fadeTargetProperty = 0.0;
}
}

View file

@ -1,24 +1,42 @@
import QtQuick 2.5
//
// Frame.qml
//
// Created by Bradley Austin Davis on 12 Jan 2016
// Copyright 2016 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 "../controls"
import QtQuick 2.5
import QtGraphicalEffects 1.0
import "../styles-uit"
import "../js/Utils.js" as Utils
Item {
id: frame
HifiConstants { id: hifi }
default property var decoration
property bool gradientsSupported: desktop.gradientsSupported
readonly property int frameMarginLeft: frameContent.frameMarginLeft
readonly property int frameMarginRight: frameContent.frameMarginRight
readonly property int frameMarginTop: frameContent.frameMarginTop
readonly property int frameMarginBottom: frameContent.frameMarginBottom
// Frames always fill their parents, but their decorations may extend
// beyond the window via negative margin sizes
anchors.fill: parent
// Convenience accessor for the window
property alias window: frame.parent
readonly property int iconSize: 24
default property var decoration;
children: [
focusShadow,
decoration,
sizeOutline,
debugZ,
sizeDrag,
sizeDrag
]
Text {
@ -35,57 +53,81 @@ Item {
window.height = newSize.y
}
RadialGradient {
id: focusShadow
width: 1.66 * window.width
height: 1.66 * window.height
x: (window.width - width) / 2
y: window.height / 2 - 0.375 * height
visible: gradientsSupported && window && window.focus && window.content.visible
gradient: Gradient {
// GradientStop position 0.5 is at full circumference of circle that fits inside the square.
GradientStop { position: 0.0; color: "#ff000000" } // black, 100% opacity
GradientStop { position: 0.333; color: "#1f000000" } // black, 12% opacity
GradientStop { position: 0.5; color: "#00000000" } // black, 0% opacity
GradientStop { position: 1.0; color: "#00000000" }
}
cached: true
}
Rectangle {
id: sizeOutline
width: window ? window.width : 0
height: window ? window.height : 0
color: "#00000000"
border.width: 4
radius: 10
x: -frameMarginLeft
y: -frameMarginTop
width: window ? window.width + frameMarginLeft + frameMarginRight + 2 : 0
height: window ? window.height + frameMarginTop + frameMarginBottom + 2 : 0
color: hifi.colors.baseGrayHighlight15
border.width: 3
border.color: hifi.colors.white50
radius: hifi.dimensions.borderRadius
visible: window ? !window.content.visible : false
}
MouseArea {
// Resize handle
id: sizeDrag
width: iconSize
height: iconSize
width: hifi.dimensions.frameIconSize
height: hifi.dimensions.frameIconSize
enabled: window ? window.resizable : false
x: window ? window.width : 0
y: window ? window.height : 0
hoverEnabled: true
x: window ? window.width + frameMarginRight - hifi.dimensions.frameIconSize : 0
y: window ? window.height + 4 : 0
property vector2d pressOrigin
property vector2d sizeOrigin
property bool hid: false
onPressed: {
console.log("Pressed on size")
//console.log("Pressed on size")
pressOrigin = Qt.vector2d(mouseX, mouseY)
sizeOrigin = Qt.vector2d(window.content.width, window.content.height)
hid = false;
}
onReleased: {
if (hid) {
window.content.visible = true
pane.visible = true
frameContent.visible = true
hid = false;
}
}
onPositionChanged: {
if (pressed) {
if (window.content.visible) {
window.content.visible = false;
if (pane.visible) {
pane.visible = false;
frameContent.visible = false
hid = true;
}
var delta = Qt.vector2d(mouseX, mouseY).minus(pressOrigin);
frame.deltaSize(delta.x, delta.y)
}
}
FontAwesome {
HiFiGlyphs {
visible: sizeDrag.enabled
rotation: -45
anchors { centerIn: parent }
horizontalAlignment: Text.AlignHCenter
text: "\uf07d"
size: iconSize / 3 * 2
style: Text.Outline; styleColor: "white"
x: -11 // Move a little to visually align
y: window.modality == Qt.ApplicationModal ? -6 : -4
text: hifi.glyphs.resizeHandle
size: hifi.dimensions.frameIconSize + 10
color: sizeDrag.containsMouse || sizeDrag.pressed
? hifi.colors.white
: (window.colorScheme == hifi.colorSchemes.dark ? hifi.colors.white50 : hifi.colors.lightGrayText80)
}
}
}

View file

@ -2,7 +2,7 @@ import QtQuick 2.5
import "."
Frame {
Item {
id: frame
Item { anchors.fill: parent }

View file

@ -1,36 +1,98 @@
//
// ModalFrame.qml
//
// Created by Bradley Austin Davis on 15 Jan 2016
// Copyright 2015 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 QtQuick 2.5
import "."
import "../controls"
import "../controls-uit"
import "../styles-uit"
Frame {
id: frame
HifiConstants { id: hifi }
Item {
anchors.fill: parent
Rectangle {
id: frameContent
Rectangle {
id: background
anchors.fill: parent
anchors.margins: -4096
visible: window.visible
color: "#7f7f7f7f";
radius: 3;
readonly property bool hasTitle: window.title != ""
readonly property int frameMarginLeft: hifi.dimensions.modalDialogMargin.x
readonly property int frameMarginRight: hifi.dimensions.modalDialogMargin.x
readonly property int frameMarginTop: hifi.dimensions.modalDialogMargin.y + (frameContent.hasTitle ? hifi.dimensions.modalDialogTitleHeight + 10 : 0)
readonly property int frameMarginBottom: hifi.dimensions.modalDialogMargin.y
signal frameClicked();
anchors {
fill: parent
topMargin: -frameMarginTop
leftMargin: -frameMarginLeft
rightMargin: -frameMarginRight
bottomMargin: -frameMarginBottom
}
Text {
y: -implicitHeight - iconSize / 2
text: window.title
elide: Text.ElideRight
font.bold: true
color: window.focus ? "white" : "gray"
style: Text.Outline;
styleColor: "black"
border {
width: hifi.dimensions.borderWidth
color: hifi.colors.lightGrayText80
}
radius: hifi.dimensions.borderRadius
color: hifi.colors.faintGray
// Enable dragging of the window
MouseArea {
anchors.fill: parent
drag.target: window
enabled: window.draggable
onClicked: window.frameClicked();
}
Item {
visible: frameContent.hasTitle
anchors.fill: parent
anchors {
topMargin: -parent.anchors.topMargin
leftMargin: -parent.anchors.leftMargin
rightMargin: -parent.anchors.rightMargin
}
Item {
width: title.width + (icon.text !== "" ? icon.width + hifi.dimensions.contentSpacing.x : 0)
x: (parent.width - width) / 2
onWidthChanged: window.titleWidth = width
HiFiGlyphs {
id: icon
text: window.iconText ? window.iconText : ""
size: window.iconSize ? window.iconSize : 30
color: hifi.colors.lightGray
visible: text != ""
anchors.verticalCenter: title.verticalCenter
anchors.left: parent.left
}
RalewayRegular {
id: title
text: window.title
elide: Text.ElideRight
color: hifi.colors.baseGrayHighlight
size: hifi.fontSizes.overlayTitle
y: -hifi.dimensions.modalDialogTitleHeight
anchors.right: parent.right
}
}
Rectangle {
anchors.left: parent.left
anchors.right: parent.right
height: 1
color: hifi.colors.lightGray
}
}
}
}

View file

@ -1,14 +1,28 @@
//
// ModalWindow.qml
//
// Created by Bradley Austin Davis on 22 Jan 2016
// Copyright 2015 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 QtQuick 2.5
import "."
Window {
id: root
anchors.centerIn: parent
ScrollingWindow {
id: window
modality: Qt.ApplicationModal
destroyOnCloseButton: true
destroyOnInvisible: true
frame: ModalFrame{}
destroyOnHidden: true
frame: ModalFrame { }
property int colorScheme: hifi.colorSchemes.light
property bool draggable: false
signal frameClicked();
anchors.centerIn: draggable ? undefined : parent
}

View file

@ -0,0 +1,157 @@
//
// Window.qml
//
// Created by Bradley Austin Davis on 12 Jan 2016
// Copyright 2016 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 QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import QtGraphicalEffects 1.0
import "."
import "../styles-uit"
// FIXME how do I set the initial position of a window without
// overriding places where the a individual client of the window
// might be setting the position with a Settings{} element?
// FIXME how to I enable dragging without allowing the window to lay outside
// of the desktop? How do I ensure when the desktop resizes all the windows
// are still at least partially visible?
Window {
id: window
HifiConstants { id: hifi }
children: [ swallower, frame, pane, activator ]
property var footer: Item { } // Optional static footer at the bottom of the dialog.
// Scrollable window content.
// FIXME this should not define any visual content in this type. The base window
// type should only consist of logic sized areas, with nothing drawn (although the
// default value for the frame property does include visual decorations)
property var pane: Item {
property bool isScrolling: scrollView.height < scrollView.contentItem.height
property int contentWidth: scrollView.width - (isScrolling ? 10 : 0)
property int scrollHeight: scrollView.height
anchors.fill: parent
anchors.rightMargin: isScrolling ? 11 : 0
Rectangle {
id: contentBackground
anchors.fill: parent
anchors.rightMargin: parent.isScrolling ? 11 : 0
color: hifi.colors.baseGray
visible: !window.hideBackground && modality != Qt.ApplicationModal
}
LinearGradient {
visible: !window.hideBackground && gradientsSupported && modality != Qt.ApplicationModal
anchors.top: contentBackground.bottom
anchors.left: contentBackground.left
width: contentBackground.width - 1
height: 4
start: Qt.point(0, 0)
end: Qt.point(0, 4)
gradient: Gradient {
GradientStop { position: 0.0; color: hifi.colors.darkGray }
GradientStop { position: 1.0; color: hifi.colors.darkGray0 }
}
cached: true
}
ScrollView {
id: scrollView
contentItem: content
horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff
verticalScrollBarPolicy: Qt.ScrollBarAsNeeded
anchors.fill: parent
anchors.rightMargin: parent.isScrolling ? 1 : 0
anchors.bottomMargin: footer.height > 0 ? footerPane.height : 0
style: ScrollViewStyle {
padding.right: -7 // Move to right away from content.
handle: Item {
implicitWidth: 8
Rectangle {
radius: 4
color: hifi.colors.white30
anchors {
fill: parent
leftMargin: 2 // Finesse size and position.
topMargin: 1
bottomMargin: 1
}
}
}
scrollBarBackground: Item {
implicitWidth: 10
Rectangle {
color: hifi.colors.darkGray30
radius: 4
anchors {
fill: parent
topMargin: -1 // Finesse size
bottomMargin: -2
}
}
}
incrementControl: Item {
visible: false
}
decrementControl: Item {
visible: false
}
}
}
Rectangle {
// Optional non-scrolling footer.
id: footerPane
anchors {
left: parent.left
bottom: parent.bottom
}
width: parent.contentWidth
height: footer.height + 2 * hifi.dimensions.contentSpacing.y + 3
color: hifi.colors.baseGray
visible: footer.height > 0
Item {
// Horizontal rule.
anchors.fill: parent
Rectangle {
width: parent.width
height: 1
y: 1 // Stop displaying content just above horizontal rule/=.
color: hifi.colors.baseGrayShadow
}
Rectangle {
width: parent.width
height: 1
y: 2
color: hifi.colors.baseGrayHighlight
}
}
Item {
anchors.fill: parent
anchors.topMargin: 3 // Horizontal rule.
children: [ footer ]
}
}
}
}

View file

@ -0,0 +1,96 @@
//
// DefaultFrame.qml
//
// Created by Bradley Austin Davis on 12 Jan 2016
// Copyright 2016 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 QtQuick 2.5
import QtGraphicalEffects 1.0
import "."
import "../styles-uit"
Frame {
HifiConstants { id: hifi }
property bool horizontalSpacers: false
property bool verticalSpacers: false
Rectangle {
// Dialog frame
id: frameContent
readonly property int frameMargin: 6
readonly property int frameMarginLeft: frameMargin + (horizontalSpacers ? 12 : 0)
readonly property int frameMarginRight: frameMargin + (horizontalSpacers ? 12 : 0)
readonly property int frameMarginTop: frameMargin + (verticalSpacers ? 12 : 0)
readonly property int frameMarginBottom: frameMargin + (verticalSpacers ? 12 : 0)
Rectangle {
visible: horizontalSpacers
anchors.left: parent.left
anchors.leftMargin: 6
anchors.verticalCenter: parent.verticalCenter
width: 8
height: window.height
color: "gray";
radius: 4
}
Rectangle {
visible: horizontalSpacers
anchors.right: parent.right
anchors.rightMargin: 6
anchors.verticalCenter: parent.verticalCenter
width: 8
height: window.height
color: "gray";
radius: 4
}
Rectangle {
visible: verticalSpacers
anchors.top: parent.top
anchors.topMargin: 6
anchors.horizontalCenter: parent.horizontalCenter
height: 8
width: window.width
color: "gray";
radius: 4
}
Rectangle {
visible: verticalSpacers
anchors.bottom: parent.bottom
anchors.bottomMargin: 6
anchors.horizontalCenter: parent.horizontalCenter
height: 8
width: window.width
color: "gray";
radius: 4
}
anchors {
leftMargin: -frameMarginLeft
rightMargin: -frameMarginRight
topMargin: -frameMarginTop
bottomMargin: -frameMarginBottom
}
anchors.fill: parent
color: hifi.colors.baseGrayHighlight40
border {
width: hifi.dimensions.borderWidth
color: hifi.colors.faintGray50
}
radius: hifi.dimensions.borderRadius / 2
// Enable dragging of the window
MouseArea {
anchors.fill: parent
drag.target: window
}
}
}

View file

@ -1,9 +1,20 @@
//
// Window.qml
//
// Created by Bradley Austin Davis on 12 Jan 2016
// Copyright 2016 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 QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import QtGraphicalEffects 1.0
import "."
import "../styles"
import "../styles-uit"
// FIXME how do I set the initial position of a window without
// overriding places where the a individual client of the window
@ -15,16 +26,36 @@ import "../styles"
Fadable {
id: window
HifiConstants { id: hifi }
//
// Signals
//
signal windowDestroyed();
//
// Native properties
//
// The Window size is the size of the content, while the frame
// decorations can extend outside it.
implicitHeight: content ? content.height : 0
implicitWidth: content ? content.width : 0
x: desktop.invalid_position; y: desktop.invalid_position;
enabled: visible
children: [ swallower, frame, content, activator ]
signal windowDestroyed();
//
// Custom properties
//
property int modality: Qt.NonModal
// Corresponds to the window shown / hidden state AS DISTINCT from window visibility.
// Window visibility should NOT be used as a proxy for any other behavior.
property bool shown: true
// FIXME workaround to deal with the face that some visual items are defined here,
// when they should be moved to a frame derived type
property bool hideBackground: false
visible: shown
enabled: visible
readonly property bool topLevelWindow: true
property string title
// Should the window be closable control?
@ -34,17 +65,23 @@ Fadable {
// Should hitting the close button hide or destroy the window?
property bool destroyOnCloseButton: true
// Should hiding the window destroy it or just hide it?
property bool destroyOnInvisible: false
// FIXME support for pinned / unpinned pending full design
// property bool pinnable: false
// property bool pinned: false
property bool destroyOnHidden: false
property bool pinnable: true
property bool pinned: false
property bool resizable: false
property bool gradientsSupported: desktop.gradientsSupported
property int colorScheme: hifi.colorSchemes.dark
property vector2d minSize: Qt.vector2d(100, 100)
property vector2d maxSize: Qt.vector2d(1280, 720)
property vector2d maxSize: Qt.vector2d(1280, 800)
// The content to place inside the window, determined by the client
default property var content
property var footer: Item { } // Optional static footer at the bottom of the dialog.
function setDefaultFocus() {} // Default function; can be overridden by dialogs.
property var rectifier: Timer {
property bool executing: false;
interval: 100
@ -65,20 +102,15 @@ Fadable {
}
}
onXChanged: rectifier.begin();
onYChanged: rectifier.begin();
// This mouse area serves to raise the window. To function, it must live
// in the window and have a higher Z-order than the content, but follow
// the position and size of frame decoration
property var activator: MouseArea {
width: frame.decoration.width
height: frame.decoration.height
x: frame.decoration.anchors.margins
y: frame.decoration.anchors.topMargin
width: frame.decoration ? frame.decoration.width : window.width
height: frame.decoration ? frame.decoration.height : window.height
x: frame.decoration ? frame.decoration.anchors.leftMargin : 0
y: frame.decoration ? frame.decoration.anchors.topMargin : 0
propagateComposedEvents: true
hoverEnabled: true
acceptedButtons: Qt.AllButtons
enabled: window.visible
onPressed: {
@ -92,10 +124,10 @@ Fadable {
// to prevent things like mouse wheel events from reaching the application and changing
// the camera if the user is scrolling through a list and gets to the end.
property var swallower: MouseArea {
width: frame.decoration.width
height: frame.decoration.height
x: frame.decoration.anchors.margins
y: frame.decoration.anchors.topMargin
width: frame.decoration ? frame.decoration.width : window.width
height: frame.decoration ? frame.decoration.height : window.height
x: frame.decoration ? frame.decoration.anchors.leftMargin : 0
y: frame.decoration ? frame.decoration.anchors.topMargin : 0
hoverEnabled: true
acceptedButtons: Qt.AllButtons
enabled: window.visible
@ -106,71 +138,119 @@ Fadable {
onWheel: {}
}
// Default to a standard frame. Can be overriden to provide custom
// frame styles, like a full desktop frame to simulate a modal window
property var frame: DefaultFrame { }
property var frame: DefaultFrame {
//window: window
}
children: [ swallower, frame, content, activator ]
//
// Handlers
//
Component.onCompleted: {
window.parentChanged.connect(raise);
raise();
centerOrReposition();
setDefaultFocus();
d.centerOrReposition();
d.updateVisibility(shown);
}
Component.onDestruction: {
window.parentChanged.disconnect(raise); // Prevent warning on shutdown
windowDestroyed();
}
function centerOrReposition() {
if (x == desktop.invalid_position && y == desktop.invalid_position) {
desktop.centerOnVisible(window);
} else {
desktop.repositionOnVisible(window);
}
}
onXChanged: rectifier.begin();
onYChanged: rectifier.begin();
onShownChanged: d.updateVisibility(shown)
onVisibleChanged: {
if (!visible && destroyOnInvisible) {
destroy();
return;
}
if (visible) {
raise();
}
enabled = visible
if (visible && parent) {
centerOrReposition();
d.centerOrReposition();
}
}
QtObject {
id: d
readonly property alias pinned: window.pinned
readonly property alias shown: window.shown
readonly property alias modality: window.modality;
function getTargetVisibility() {
if (!window.shown) {
return false;
}
if (modality !== Qt.NonModal) {
return true;
}
if (pinned) {
return true;
}
if (desktop && !desktop.pinned) {
return true;
}
return false;
}
// The force flag causes all windows to fade back in, because a window was shown
readonly property alias visible: window.visible
function updateVisibility(force) {
if (force && !pinned && desktop.pinned) {
// Change the pinned state (which in turn will call us again)
desktop.pinned = false;
return;
}
var targetVisibility = getTargetVisibility();
if (targetVisibility === visible) {
return;
}
if (targetVisibility) {
fadeIn(function() {
if (force) {
window.raise();
}
});
} else {
fadeOut(function() {
if (!window.shown && window.destroyOnHidden) {
window.destroy();
}
});
}
}
function centerOrReposition() {
if (x == desktop.invalid_position && y == desktop.invalid_position) {
desktop.centerOnVisible(window);
} else {
desktop.repositionOnVisible(window);
}
}
}
// When the desktop pinned state changes, automatically handle the current windows
Connections { target: desktop; onPinnedChanged: d.updateVisibility() }
function raise() {
if (visible && parent) {
desktop.raise(window)
}
}
function pin() {
// pinned = ! pinned
function setPinned() {
pinned = !pinned
}
// our close function performs the same way as the OffscreenUI class:
// don't do anything but manipulate the targetVisible flag and let the other
// mechanisms decide if the window should be destroyed after the close
// animation completes
// FIXME using this close function messes up the visibility signals received by the
// type and it's derived types
// function close() {
// console.log("Closing " + window)
// if (destroyOnCloseButton) {
// destroyOnInvisible = true
// }
// visible = false;
// }
function framedRect() {
if (!frame || !frame.decoration) {
return Qt.rect(0, 0, window.width, window.height)
@ -180,7 +260,6 @@ Fadable {
window.height - frame.decoration.anchors.topMargin - frame.decoration.anchors.bottomMargin)
}
Keys.onPressed: {
switch(event.key) {
case Qt.Key_Control:
@ -189,10 +268,9 @@ Fadable {
case Qt.Key_Alt:
break;
case Qt.Key_W:
if (window.closable && (event.modifiers === Qt.ControlModifier)) {
visible = false
shown = false
event.accepted = true
}
// fall through

View file

@ -132,6 +132,7 @@
#include "scripting/WebWindowClass.h"
#include "scripting/WindowScriptingInterface.h"
#include "scripting/ControllerScriptingInterface.h"
#include "scripting/ToolbarScriptingInterface.h"
#include "scripting/RatesScriptingInterface.h"
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
#include "SpeechRecognizer.h"
@ -438,6 +439,7 @@ bool setupEssentials(int& argc, char** argv) {
DependencyManager::set<WindowScriptingInterface>();
DependencyManager::set<HMDScriptingInterface>();
DependencyManager::set<ResourceScriptingInterface>();
DependencyManager::set<ToolbarScriptingInterface>();
DependencyManager::set<UserActivityLoggerScriptingInterface>();
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
@ -1534,7 +1536,7 @@ void Application::initializeUi() {
rootContext->setContextProperty("Overlays", &_overlays);
rootContext->setContextProperty("Window", DependencyManager::get<WindowScriptingInterface>().data());
rootContext->setContextProperty("Menu", MenuScriptingInterface::getInstance());
rootContext->setContextProperty("MenuInterface", MenuScriptingInterface::getInstance());
rootContext->setContextProperty("Stats", Stats::getInstance());
rootContext->setContextProperty("Settings", SettingsScriptingInterface::getInstance());
rootContext->setContextProperty("ScriptDiscoveryService", DependencyManager::get<ScriptEngines>().data());
@ -2185,7 +2187,8 @@ void Application::keyPressEvent(QKeyEvent* event) {
case Qt::Key_X:
if (isShifted && isMeta) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
offscreenUi->getRootContext()->engine()->clearComponentCache();
offscreenUi->togglePinned();
//offscreenUi->getRootContext()->engine()->clearComponentCache();
//OffscreenUi::information("Debugging", "Component cache cleared");
// placeholder for dialogs being converted to QML.
}
@ -2834,7 +2837,6 @@ void Application::idle(float nsecsElapsed) {
if (firstIdle) {
firstIdle = false;
connect(offscreenUi.data(), &OffscreenUi::showDesktop, this, &Application::showDesktop);
_overlayConductor.setEnabled(Menu::getInstance()->isOptionChecked(MenuOption::Overlays));
}
PROFILE_RANGE(__FUNCTION__);
@ -3334,13 +3336,13 @@ void Application::updateThreads(float deltaTime) {
}
void Application::toggleOverlays() {
auto newOverlaysVisible = !_overlayConductor.getEnabled();
Menu::getInstance()->setIsOptionChecked(MenuOption::Overlays, newOverlaysVisible);
_overlayConductor.setEnabled(newOverlaysVisible);
auto menu = Menu::getInstance();
menu->setIsOptionChecked(MenuOption::Overlays, menu->isOptionChecked(MenuOption::Overlays));
}
void Application::setOverlaysVisible(bool visible) {
_overlayConductor.setEnabled(visible);
auto menu = Menu::getInstance();
menu->setIsOptionChecked(MenuOption::Overlays, true);
}
void Application::cycleCamera() {
@ -4325,6 +4327,7 @@ void Application::resetSensors(bool andReload) {
DependencyManager::get<DdeFaceTracker>()->reset();
DependencyManager::get<EyeTracker>()->reset();
getActiveDisplayPlugin()->resetSensors();
_overlayConductor.centerUI();
getMyAvatar()->reset(andReload);
QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(), "reset", Qt::QueuedConnection);
}
@ -4641,6 +4644,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
RayToOverlayIntersectionResultFromScriptValue);
scriptEngine->registerGlobalObject("Desktop", DependencyManager::get<DesktopScriptingInterface>().data());
scriptEngine->registerGlobalObject("Toolbars", DependencyManager::get<ToolbarScriptingInterface>().data());
scriptEngine->registerGlobalObject("Window", DependencyManager::get<WindowScriptingInterface>().data());
scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter,
@ -5422,9 +5426,7 @@ void Application::readArgumentsFromLocalSocket() const {
}
void Application::showDesktop() {
if (!_overlayConductor.getEnabled()) {
_overlayConductor.setEnabled(true);
}
Menu::getInstance()->setIsOptionChecked(MenuOption::Overlays, true);
}
CompositorHelper& Application::getApplicationCompositor() const {

View file

@ -256,8 +256,7 @@ Menu::Menu() {
UNSPECIFIED_POSITION, "Advanced");
// View > Overlays
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Overlays, 0, true,
qApp, SLOT(setOverlaysVisible(bool)));
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Overlays, 0, true);
// Navigate menu ----------------------------------
MenuWrapper* navigateMenu = addMenu("Navigate");

View file

@ -722,7 +722,7 @@ void MyAvatar::saveData() {
settings.setValue("displayName", _displayName);
settings.setValue("collisionSoundURL", _collisionSoundURL);
settings.setValue("useSnapTurn", _useSnapTurn);
settings.setValue("clearOverlayWhenDriving", _clearOverlayWhenDriving);
settings.setValue("clearOverlayWhenMoving", _clearOverlayWhenMoving);
settings.endGroup();
}
@ -842,7 +842,7 @@ void MyAvatar::loadData() {
setDisplayName(settings.value("displayName").toString());
setCollisionSoundURL(settings.value("collisionSoundURL", DEFAULT_AVATAR_COLLISION_SOUND_URL).toString());
setSnapTurn(settings.value("useSnapTurn", _useSnapTurn).toBool());
setClearOverlayWhenDriving(settings.value("clearOverlayWhenDriving", _clearOverlayWhenDriving).toBool());
setClearOverlayWhenMoving(settings.value("clearOverlayWhenMoving", _clearOverlayWhenMoving).toBool());
settings.endGroup();
@ -1248,8 +1248,7 @@ void MyAvatar::prepareForPhysicsSimulation() {
_characterController.setPositionAndOrientation(getPosition(), getOrientation());
if (qApp->isHMDMode()) {
bool hasDriveInput = fabsf(_driveKeys[TRANSLATE_X]) > 0.0f || fabsf(_driveKeys[TRANSLATE_Z]) > 0.0f;
_follow.prePhysicsUpdate(*this, deriveBodyFromHMDSensor(), _bodySensorMatrix, hasDriveInput);
_follow.prePhysicsUpdate(*this, deriveBodyFromHMDSensor(), _bodySensorMatrix, hasDriveInput());
} else {
_follow.deactivate();
}
@ -2134,3 +2133,7 @@ bool MyAvatar::didTeleport() {
lastPosition = pos;
return (changeInPosition.length() > MAX_AVATAR_MOVEMENT_PER_FRAME);
}
bool MyAvatar::hasDriveInput() const {
return fabsf(_driveKeys[TRANSLATE_X]) > 0.0f || fabsf(_driveKeys[TRANSLATE_Y]) > 0.0f || fabsf(_driveKeys[TRANSLATE_Z]) > 0.0f;
}

View file

@ -159,8 +159,8 @@ public:
Q_INVOKABLE bool getSnapTurn() const { return _useSnapTurn; }
Q_INVOKABLE void setSnapTurn(bool on) { _useSnapTurn = on; }
Q_INVOKABLE bool getClearOverlayWhenDriving() const { return _clearOverlayWhenDriving; }
Q_INVOKABLE void setClearOverlayWhenDriving(bool on) { _clearOverlayWhenDriving = on; }
Q_INVOKABLE bool getClearOverlayWhenMoving() const { return _clearOverlayWhenMoving; }
Q_INVOKABLE void setClearOverlayWhenMoving(bool on) { _clearOverlayWhenMoving = on; }
Q_INVOKABLE void setHMDLeanRecenterEnabled(bool value) { _hmdLeanRecenterEnabled = value; }
Q_INVOKABLE bool getHMDLeanRecenterEnabled() const { return _hmdLeanRecenterEnabled; }
@ -266,6 +266,8 @@ public:
controller::Pose getLeftHandControllerPoseInAvatarFrame() const;
controller::Pose getRightHandControllerPoseInAvatarFrame() const;
bool hasDriveInput() const;
Q_INVOKABLE void setCharacterControllerEnabled(bool enabled);
Q_INVOKABLE bool getCharacterControllerEnabled();
@ -403,7 +405,7 @@ private:
QString _fullAvatarModelName;
QUrl _animGraphUrl {""};
bool _useSnapTurn { true };
bool _clearOverlayWhenDriving { false };
bool _clearOverlayWhenMoving { true };
// cache of the current HMD sensor position and orientation
// in sensor space.

View file

@ -16,6 +16,7 @@
#include "Application.h"
#include "MainWindow.h"
#include <display-plugins/CompositorHelper.h>
int DesktopScriptingInterface::getWidth() {
QSize size = qApp->getWindow()->windowHandle()->screen()->virtualSize();
@ -25,3 +26,8 @@ int DesktopScriptingInterface::getHeight() {
QSize size = qApp->getWindow()->windowHandle()->screen()->virtualSize();
return size.height();
}
void DesktopScriptingInterface::setOverlayAlpha(float alpha) {
qApp->getApplicationCompositor().setAlpha(alpha);
}

View file

@ -22,6 +22,8 @@ class DesktopScriptingInterface : public QObject, public Dependency {
Q_PROPERTY(int height READ getHeight) // Physical height of screen(s) including task bars and system menus
public:
Q_INVOKABLE void setOverlayAlpha(float alpha);
int getWidth();
int getHeight();
};

View file

@ -105,3 +105,25 @@ QString HMDScriptingInterface::preferredAudioInput() const {
QString HMDScriptingInterface::preferredAudioOutput() const {
return qApp->getActiveDisplayPlugin()->getPreferredAudioOutDevice();
}
bool HMDScriptingInterface::setHandLasers(int hands, bool enabled, const glm::vec4& color, const glm::vec3& direction) const {
return qApp->getActiveDisplayPlugin()->setHandLaser(hands,
enabled ? DisplayPlugin::HandLaserMode::Overlay : DisplayPlugin::HandLaserMode::None,
color, direction);
}
void HMDScriptingInterface::disableHandLasers(int hands) const {
qApp->getActiveDisplayPlugin()->setHandLaser(hands, DisplayPlugin::HandLaserMode::None);
}
bool HMDScriptingInterface::suppressKeyboard() {
return qApp->getActiveDisplayPlugin()->suppressKeyboard();
}
void HMDScriptingInterface::unsuppressKeyboard() {
qApp->getActiveDisplayPlugin()->unsuppressKeyboard();
}
bool HMDScriptingInterface::isKeyboardVisible() {
return qApp->getActiveDisplayPlugin()->isKeyboardVisible();
}

View file

@ -1,4 +1,4 @@
//
// HMDScriptingInterface.h
// interface/src/scripting
//
@ -12,6 +12,8 @@
#ifndef hifi_HMDScriptingInterface_h
#define hifi_HMDScriptingInterface_h
#include <atomic>
#include <QtScript/QScriptValue>
class QScriptContext;
class QScriptEngine;
@ -31,12 +33,28 @@ public:
Q_INVOKABLE glm::vec3 calculateRayUICollisionPoint(const glm::vec3& position, const glm::vec3& direction) const;
Q_INVOKABLE glm::vec2 overlayFromWorldPoint(const glm::vec3& position) const;
Q_INVOKABLE glm::vec3 worldPointFromOverlay(const glm::vec2& overlay) const;
Q_INVOKABLE glm::vec2 sphericalToOverlay(const glm::vec2 & sphericalPos) const;
Q_INVOKABLE glm::vec2 overlayToSpherical(const glm::vec2 & overlayPos) const;
Q_INVOKABLE QString preferredAudioInput() const;
Q_INVOKABLE QString preferredAudioOutput() const;
Q_INVOKABLE bool setHandLasers(int hands, bool enabled, const glm::vec4& color, const glm::vec3& direction) const;
Q_INVOKABLE void disableHandLasers(int hands) const;
/// Suppress the activation of any on-screen keyboard so that a script operation will
/// not be interrupted by a keyboard popup
/// Returns false if there is already an active keyboard displayed.
/// Clients should re-enable the keyboard when the operation is complete and ensure
/// that they balance any call to suppressKeyboard() that returns true with a corresponding
/// call to unsuppressKeyboard() within a reasonable amount of time
Q_INVOKABLE bool suppressKeyboard();
/// Enable the keyboard following a suppressKeyboard call
Q_INVOKABLE void unsuppressKeyboard();
/// Query the display plugin to determine the current VR keyboard visibility
Q_INVOKABLE bool isKeyboardVisible();
public:
HMDScriptingInterface();
static QScriptValue getHUDLookAtPosition2D(QScriptContext* context, QScriptEngine* engine);

View file

@ -0,0 +1,115 @@
//
// Created by Bradley Austin Davis on 2016-06-16
// Copyright 2013-2016 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 "ToolbarScriptingInterface.h"
#include <OffscreenUi.h>
class QmlWrapper : public QObject {
Q_OBJECT
public:
QmlWrapper(QObject* qmlObject, QObject* parent = nullptr)
: QObject(parent), _qmlObject(qmlObject) {
}
Q_INVOKABLE void writeProperty(QString propertyName, QVariant propertyValue) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
offscreenUi->executeOnUiThread([=] {
_qmlObject->setProperty(propertyName.toStdString().c_str(), propertyValue);
});
}
Q_INVOKABLE void writeProperties(QVariant propertyMap) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
offscreenUi->executeOnUiThread([=] {
QVariantMap map = propertyMap.toMap();
for (const QString& key : map.keys()) {
_qmlObject->setProperty(key.toStdString().c_str(), map[key]);
}
});
}
Q_INVOKABLE QVariant readProperty(const QString& propertyName) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
return offscreenUi->returnFromUiThread([&]()->QVariant {
return _qmlObject->property(propertyName.toStdString().c_str());
});
}
Q_INVOKABLE QVariant readProperties(const QVariant& propertyList) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
return offscreenUi->returnFromUiThread([&]()->QVariant {
QVariantMap result;
for (const QVariant& property : propertyList.toList()) {
QString propertyString = property.toString();
result.insert(propertyString, _qmlObject->property(propertyString.toStdString().c_str()));
}
return result;
});
}
protected:
QObject* _qmlObject{ nullptr };
};
class ToolbarButtonProxy : public QmlWrapper {
Q_OBJECT
public:
ToolbarButtonProxy(QObject* qmlObject, QObject* parent = nullptr) : QmlWrapper(qmlObject, parent) {
connect(qmlObject, SIGNAL(clicked()), this, SIGNAL(clicked()));
}
signals:
void clicked();
};
class ToolbarProxy : public QmlWrapper {
Q_OBJECT
public:
ToolbarProxy(QObject* qmlObject, QObject* parent = nullptr) : QmlWrapper(qmlObject, parent) { }
Q_INVOKABLE QObject* addButton(const QVariant& properties) {
QVariant resultVar;
bool invokeResult = QMetaObject::invokeMethod(_qmlObject, "addButton", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QVariant, resultVar), Q_ARG(QVariant, properties));
if (!invokeResult) {
return nullptr;
}
QObject* rawButton = qvariant_cast<QObject *>(resultVar);
if (!rawButton) {
return nullptr;
}
return new ToolbarButtonProxy(rawButton, this);
}
};
QObject* ToolbarScriptingInterface::getToolbar(const QString& toolbarId) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
auto desktop = offscreenUi->getDesktop();
QVariant resultVar;
bool invokeResult = QMetaObject::invokeMethod(desktop, "getToolbar", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QVariant, resultVar), Q_ARG(QVariant, toolbarId));
if (!invokeResult) {
return nullptr;
}
QObject* rawToolbar = qvariant_cast<QObject *>(resultVar);
if (!rawToolbar) {
return nullptr;
}
return new ToolbarProxy(rawToolbar);
}
#include "ToolbarScriptingInterface.moc"

View file

@ -0,0 +1,26 @@
//
// Created by Bradley Austin Davis on 2016-06-16
// Copyright 2013-2016 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_ToolbarScriptingInterface_h
#define hifi_ToolbarScriptingInterface_h
#include <mutex>
#include <QtCore/QObject>
#include <DependencyManager.h>
class ToolbarProxy;
class ToolbarScriptingInterface : public QObject, public Dependency {
Q_OBJECT
public:
Q_INVOKABLE QObject* getToolbar(const QString& toolbarId);
};
#endif // hifi_ToolbarScriptingInterface_h

View file

@ -17,179 +17,134 @@
#include "OverlayConductor.h"
OverlayConductor::OverlayConductor() {
}
OverlayConductor::~OverlayConductor() {
}
void OverlayConductor::update(float dt) {
bool OverlayConductor::headOutsideOverlay() const {
glm::mat4 hmdMat = qApp->getHMDSensorPose();
glm::vec3 hmdPos = extractTranslation(hmdMat);
glm::vec3 hmdForward = transformVectorFast(hmdMat, glm::vec3(0.0f, 0.0f, -1.0f));
updateMode();
Transform uiTransform = qApp->getApplicationCompositor().getModelTransform();
glm::vec3 uiPos = uiTransform.getTranslation();
glm::vec3 uiForward = uiTransform.getRotation() * glm::vec3(0.0f, 0.0f, -1.0f);
switch (_mode) {
case SITTING: {
// when sitting, the overlay is at the origin, facing down the -z axis.
// the camera is taken directly from the HMD.
Transform identity;
qApp->getApplicationCompositor().setModelTransform(identity);
qApp->getApplicationCompositor().setCameraBaseTransform(identity);
break;
}
case STANDING: {
// when standing, the overlay is at a reference position, which is set when the overlay is
// enabled. The camera is taken directly from the HMD, but in world space.
// So the sensorToWorldMatrix must be applied.
MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
Transform t;
t.evalFromRawMatrix(myAvatar->getSensorToWorldMatrix());
qApp->getApplicationCompositor().setCameraBaseTransform(t);
// detect when head moves out side of sweet spot, or looks away.
mat4 headMat = myAvatar->getSensorToWorldMatrix() * qApp->getHMDSensorPose();
vec3 headWorldPos = extractTranslation(headMat);
vec3 headForward = glm::quat_cast(headMat) * glm::vec3(0.0f, 0.0f, -1.0f);
Transform modelXform = qApp->getApplicationCompositor().getModelTransform();
vec3 compositorWorldPos = modelXform.getTranslation();
vec3 compositorForward = modelXform.getRotation() * glm::vec3(0.0f, 0.0f, -1.0f);
const float MAX_COMPOSITOR_DISTANCE = 0.6f;
const float MAX_COMPOSITOR_ANGLE = 110.0f;
if (_enabled && (glm::distance(headWorldPos, compositorWorldPos) > MAX_COMPOSITOR_DISTANCE ||
glm::dot(headForward, compositorForward) < cosf(glm::radians(MAX_COMPOSITOR_ANGLE)))) {
// fade out the overlay
setEnabled(false);
}
break;
}
case FLAT:
// do nothing
break;
const float MAX_COMPOSITOR_DISTANCE = 0.99f; // If you're 1m from center of ui sphere, you're at the surface.
const float MAX_COMPOSITOR_ANGLE = 180.0f; // rotation check is effectively disabled
if (glm::distance(uiPos, hmdPos) > MAX_COMPOSITOR_DISTANCE ||
glm::dot(uiForward, hmdForward) < cosf(glm::radians(MAX_COMPOSITOR_ANGLE))) {
return true;
}
return false;
}
void OverlayConductor::updateMode() {
bool OverlayConductor::updateAvatarIsAtRest() {
MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
if (myAvatar->getClearOverlayWhenDriving()) {
float speed = glm::length(myAvatar->getVelocity());
const float MIN_DRIVING = 0.2f;
const float MAX_NOT_DRIVING = 0.01f;
const quint64 REQUIRED_USECS_IN_NEW_MODE_BEFORE_INVISIBLE = 200 * 1000;
const quint64 REQUIRED_USECS_IN_NEW_MODE_BEFORE_VISIBLE = 1000 * 1000;
bool nowDriving = _driving; // Assume current _driving mode unless...
if (speed > MIN_DRIVING) { // ... we're definitely moving...
nowDriving = true;
} else if (speed < MAX_NOT_DRIVING) { // ... or definitely not.
nowDriving = false;
}
// Check that we're in this new mode for long enough to really trigger a transition.
if (nowDriving == _driving) { // If there's no change in state, clear any attepted timer.
_timeInPotentialMode = 0;
} else if (_timeInPotentialMode == 0) { // We've just changed with no timer, so start timing now.
_timeInPotentialMode = usecTimestampNow();
} else if ((usecTimestampNow() - _timeInPotentialMode) > (nowDriving ? REQUIRED_USECS_IN_NEW_MODE_BEFORE_INVISIBLE : REQUIRED_USECS_IN_NEW_MODE_BEFORE_VISIBLE)) {
_timeInPotentialMode = 0; // a real transition
if (nowDriving) {
_wantsOverlays = Menu::getInstance()->isOptionChecked(MenuOption::Overlays);
} else { // reset when coming out of driving
_mode = FLAT; // Seems appropriate to let things reset, below, after the following.
// All reset of, e.g., room-scale location as though by apostrophe key, without all the other adjustments.
qApp->getActiveDisplayPlugin()->resetSensors();
myAvatar->reset(true, false, false);
}
if (_wantsOverlays) {
setEnabled(!nowDriving, false);
}
_driving = nowDriving;
} // Else haven't accumulated enough time in new mode, but keep timing.
const quint64 REST_ENABLE_TIME_USECS = 1000 * 1000; // 1 s
const quint64 REST_DISABLE_TIME_USECS = 200 * 1000; // 200 ms
const float AT_REST_THRESHOLD = 0.01f;
bool desiredAtRest = glm::length(myAvatar->getVelocity()) < AT_REST_THRESHOLD;
if (desiredAtRest != _desiredAtRest) {
// start timer
_desiredAtRestTimer = usecTimestampNow() + (desiredAtRest ? REST_ENABLE_TIME_USECS : REST_DISABLE_TIME_USECS);
}
Mode newMode;
if (qApp->isHMDMode()) {
newMode = SITTING;
_desiredAtRest = desiredAtRest;
if (_desiredAtRestTimer != 0 && usecTimestampNow() > _desiredAtRestTimer) {
// timer expired
// change state!
_currentAtRest = _desiredAtRest;
// disable timer
_desiredAtRestTimer = 0;
}
return _currentAtRest;
}
bool OverlayConductor::updateAvatarHasDriveInput() {
MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
const quint64 DRIVE_ENABLE_TIME_USECS = 200 * 1000; // 200 ms
const quint64 DRIVE_DISABLE_TIME_USECS = 1000 * 1000; // 1 s
bool desiredDriving = myAvatar->hasDriveInput();
if (desiredDriving != _desiredDriving) {
// start timer
_desiredDrivingTimer = usecTimestampNow() + (desiredDriving ? DRIVE_ENABLE_TIME_USECS : DRIVE_DISABLE_TIME_USECS);
}
_desiredDriving = desiredDriving;
if (_desiredDrivingTimer != 0 && usecTimestampNow() > _desiredDrivingTimer) {
// timer expired
// change state!
_currentDriving = _desiredDriving;
// disable timer
_desiredDrivingTimer = 0;
}
return _currentDriving;
}
void OverlayConductor::centerUI() {
// place the overlay at the current hmd position in sensor space
auto camMat = cancelOutRollAndPitch(qApp->getHMDSensorPose());
qApp->getApplicationCompositor().setModelTransform(Transform(camMat));
}
void OverlayConductor::update(float dt) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
bool currentVisible = !offscreenUi->getDesktop()->property("pinned").toBool();
MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
// centerUI when hmd mode is first enabled and mounted
if (qApp->isHMDMode() && qApp->getActiveDisplayPlugin()->isDisplayVisible()) {
if (!_hmdMode) {
_hmdMode = true;
centerUI();
}
} else {
newMode = FLAT;
_hmdMode = false;
}
if (newMode != _mode) {
switch (newMode) {
case SITTING: {
// enter the SITTING state
// place the overlay at origin
Transform identity;
qApp->getApplicationCompositor().setModelTransform(identity);
break;
}
case STANDING: { // STANDING mode is not currently used.
// enter the STANDING state
// place the overlay at the current hmd position in world space
auto camMat = cancelOutRollAndPitch(myAvatar->getSensorToWorldMatrix() * qApp->getHMDSensorPose());
Transform t;
t.setTranslation(extractTranslation(camMat));
t.setRotation(glm::quat_cast(camMat));
qApp->getApplicationCompositor().setModelTransform(t);
break;
}
bool prevDriving = _currentDriving;
bool isDriving = updateAvatarHasDriveInput();
bool drivingChanged = prevDriving != isDriving;
bool isAtRest = updateAvatarIsAtRest();
case FLAT:
// do nothing
break;
if (_flags & SuppressedByDrive) {
if (!isDriving) {
_flags &= ~SuppressedByDrive;
}
} else {
if (myAvatar->getClearOverlayWhenMoving() && drivingChanged && isDriving) {
_flags |= SuppressedByDrive;
}
}
_mode = newMode;
}
void OverlayConductor::setEnabled(bool enabled, bool toggleQmlEvents) {
if (enabled == _enabled) {
return;
if (_flags & SuppressedByHead) {
if (isAtRest) {
_flags &= ~SuppressedByHead;
}
} else {
if (_hmdMode && headOutsideOverlay()) {
_flags |= SuppressedByHead;
}
}
if (toggleQmlEvents) { // Could recurse on us with the wrong toggleQmlEvents flag, and not need in the !toggleQmlEvent case anyway.
Menu::getInstance()->setIsOptionChecked(MenuOption::Overlays, enabled);
}
_enabled = enabled; // set the new value
// if the new state is visible/enabled...
if (_enabled) {
// alpha fadeIn the overlay mesh.
qApp->getApplicationCompositor().fadeIn();
// enable mouse clicks from script
qApp->getOverlays().enable();
// enable QML events
if (toggleQmlEvents) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
offscreenUi->getRootItem()->setEnabled(true);
}
if (_mode == STANDING) {
// place the overlay at the current hmd position in world space
MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
auto camMat = cancelOutRollAndPitch(myAvatar->getSensorToWorldMatrix() * qApp->getHMDSensorPose());
Transform t;
t.setTranslation(extractTranslation(camMat));
t.setRotation(glm::quat_cast(camMat));
qApp->getApplicationCompositor().setModelTransform(t);
}
} else { // other wise, if the new state is hidden/not enabled
// alpha fadeOut the overlay mesh.
qApp->getApplicationCompositor().fadeOut();
// disable mouse clicks from script
qApp->getOverlays().disable();
// disable QML events
if (toggleQmlEvents) { // I'd really rather always do this, but it looses drive state. bugzid:501
auto offscreenUi = DependencyManager::get<OffscreenUi>();
offscreenUi->getRootItem()->setEnabled(false);
bool targetVisible = Menu::getInstance()->isOptionChecked(MenuOption::Overlays) && (0 == (_flags & SuppressMask));
if (targetVisible != currentVisible) {
offscreenUi->setPinned(!targetVisible);
if (targetVisible && _hmdMode) {
centerUI();
}
}
}
bool OverlayConductor::getEnabled() const {
return _enabled;
}

View file

@ -17,23 +17,31 @@ public:
~OverlayConductor();
void update(float dt);
void setEnabled(bool enable, bool toggleQmlEvents = true);
bool getEnabled() const;
void centerUI();
private:
void updateMode();
bool headOutsideOverlay() const;
bool updateAvatarHasDriveInput();
bool updateAvatarIsAtRest();
enum Mode {
FLAT,
SITTING,
STANDING
enum SupressionFlags {
SuppressedByDrive = 0x01,
SuppressedByHead = 0x02,
SuppressMask = 0x03,
};
Mode _mode { FLAT };
bool _enabled { false };
bool _driving { false };
quint64 _timeInPotentialMode { 0 };
bool _wantsOverlays { true };
uint8_t _flags { SuppressedByDrive };
bool _hmdMode { false };
// used by updateAvatarHasDriveInput
quint64 _desiredDrivingTimer { 0 };
bool _desiredDriving { false };
bool _currentDriving { false };
// used by updateAvatarIsAtRest
quint64 _desiredAtRestTimer { 0 };
bool _desiredAtRest { true };
bool _currentAtRest { true };
};
#endif

View file

@ -62,9 +62,9 @@ void setupPreferences() {
preferences->addPreference(new CheckPreference(AVATAR_BASICS, "Snap turn when in HMD", getter, setter));
}
{
auto getter = [=]()->bool {return myAvatar->getClearOverlayWhenDriving(); };
auto setter = [=](bool value) { myAvatar->setClearOverlayWhenDriving(value); };
preferences->addPreference(new CheckPreference(AVATAR_BASICS, "Clear overlays when driving", getter, setter));
auto getter = [=]()->bool {return myAvatar->getClearOverlayWhenMoving(); };
auto setter = [=](bool value) { myAvatar->setClearOverlayWhenMoving(value); };
preferences->addPreference(new CheckPreference(AVATAR_BASICS, "Clear overlays when moving", getter, setter));
}
{
auto getter = []()->QString { return Snapshot::snapshotsLocation.get(); };

View file

@ -32,6 +32,7 @@ class Mapping;
using MappingPointer = std::shared_ptr<Mapping>;
using MappingList = std::list<MappingPointer>;
struct Pose;
}
#endif

View file

@ -44,7 +44,8 @@ namespace controller {
LS_TOUCH,
LEFT_THUMB_UP,
LS_CENTER,
LS_OUTER,
LS_X,
LS_Y,
RIGHT_PRIMARY_THUMB,
RIGHT_SECONDARY_THUMB,
@ -53,7 +54,8 @@ namespace controller {
RS_TOUCH,
RIGHT_THUMB_UP,
RS_CENTER,
RS_OUTER,
RS_X,
RS_Y,
LEFT_PRIMARY_INDEX,
LEFT_SECONDARY_INDEX,

View file

@ -336,7 +336,9 @@ void CompositorHelper::computeHmdPickRay(const glm::vec2& cursorPos, glm::vec3&
}
glm::mat4 CompositorHelper::getUiTransform() const {
return _currentCamera * glm::inverse(_currentDisplayPlugin->getHeadPose());
glm::mat4 modelMat;
_modelTransform.getMatrix(modelMat);
return _currentCamera * glm::inverse(_currentDisplayPlugin->getHeadPose()) * modelMat;
}
//Finds the collision point of a world space ray
@ -346,7 +348,7 @@ bool CompositorHelper::calculateRayUICollisionPoint(const glm::vec3& position, c
auto relativePosition = vec3(relativePosition4) / relativePosition4.w;
auto relativeDirection = glm::inverse(glm::quat_cast(UITransform)) * direction;
float uiRadius = _oculusUIRadius; // * myAvatar->getUniformScale(); // FIXME - how do we want to handle avatar scale
float uiRadius = _hmdUIRadius; // * myAvatar->getUniformScale(); // FIXME - how do we want to handle avatar scale
float instersectionDistance;
if (raySphereIntersect(relativeDirection, relativePosition, uiRadius, &instersectionDistance)){
@ -407,84 +409,25 @@ void CompositorHelper::updateTooltips() {
//}
}
static const float FADE_DURATION = 500.0f;
static const float FADE_IN_ALPHA = 1.0f;
static const float FADE_OUT_ALPHA = 0.0f;
void CompositorHelper::startFadeFailsafe(float endValue) {
_fadeStarted = usecTimestampNow();
_fadeFailsafeEndValue = endValue;
const int SLIGHT_DELAY = 10;
QTimer::singleShot(FADE_DURATION + SLIGHT_DELAY, [this]{
checkFadeFailsafe();
});
}
void CompositorHelper::checkFadeFailsafe() {
auto elapsedInFade = usecTimestampNow() - _fadeStarted;
if (elapsedInFade > FADE_DURATION) {
setAlpha(_fadeFailsafeEndValue);
}
}
void CompositorHelper::fadeIn() {
_fadeInAlpha = true;
_alphaPropertyAnimation->setDuration(FADE_DURATION);
_alphaPropertyAnimation->setStartValue(_alpha);
_alphaPropertyAnimation->setEndValue(FADE_IN_ALPHA);
_alphaPropertyAnimation->start();
// Sometimes, this "QPropertyAnimation" fails to complete the animation, and we end up with a partially faded
// state. So we will also have this fail-safe, where we record the timestamp of the fadeRequest, and the target
// value of the fade, and if after that time we still haven't faded all the way, we will kick it to the final
// fade value
startFadeFailsafe(FADE_IN_ALPHA);
}
void CompositorHelper::fadeOut() {
_fadeInAlpha = false;
_alphaPropertyAnimation->setDuration(FADE_DURATION);
_alphaPropertyAnimation->setStartValue(_alpha);
_alphaPropertyAnimation->setEndValue(FADE_OUT_ALPHA);
_alphaPropertyAnimation->start();
startFadeFailsafe(FADE_OUT_ALPHA);
}
void CompositorHelper::toggle() {
if (_fadeInAlpha) {
fadeOut();
} else {
fadeIn();
}
}
// eyePose and headPosition are in sensor space.
// the resulting matrix should be in view space.
glm::mat4 CompositorHelper::getReticleTransform(const glm::mat4& eyePose, const glm::vec3& headPosition) const {
glm::mat4 result;
if (isHMD()) {
vec3 reticleScale = vec3(Cursor::Manager::instance().getScale() * reticleSize);
auto reticlePosition = getReticlePosition();
auto spherical = overlayToSpherical(reticlePosition);
// The pointer transform relative to the sensor
auto pointerTransform = glm::mat4_cast(quat(vec3(-spherical.y, spherical.x, 0.0f))) * glm::translate(mat4(), vec3(0, 0, -1));
float reticleDepth = getReticleDepth();
if (reticleDepth != 1.0f) {
// Cursor position in UI space
auto cursorPosition = vec3(pointerTransform[3]) / pointerTransform[3].w;
// Ray to the cursor, in UI space
auto cursorRay = glm::normalize(cursorPosition - headPosition) * reticleDepth;
// Move the ray to be relative to the head pose
pointerTransform[3] = vec4(cursorRay + headPosition, 1);
// Scale up the cursor because of distance
reticleScale *= reticleDepth;
vec2 spherical = overlayToSpherical(getReticlePosition());
vec3 overlaySurfacePoint = getPoint(spherical.x, spherical.y); // overlay space
vec3 sensorSurfacePoint = _modelTransform.transform(overlaySurfacePoint); // sensor space
vec3 d = sensorSurfacePoint - headPosition;
vec3 reticlePosition;
if (glm::length(d) >= EPSILON) {
d = glm::normalize(d);
} else {
d = glm::normalize(overlaySurfacePoint);
}
glm::mat4 overlayXfm;
_modelTransform.getMatrix(overlayXfm);
pointerTransform = overlayXfm * pointerTransform;
pointerTransform = glm::inverse(eyePose) * pointerTransform;
result = glm::scale(pointerTransform, reticleScale);
reticlePosition = headPosition + (d * getReticleDepth());
quat reticleOrientation = glm::quat_cast(_currentDisplayPlugin->getHeadPose());
vec3 reticleScale = vec3(Cursor::Manager::instance().getScale() * reticleSize * getReticleDepth());
return glm::inverse(eyePose) * createMatFromScaleQuatAndPos(reticleScale, reticleOrientation, reticlePosition);
} else {
static const float CURSOR_PIXEL_SIZE = 32.0f;
const auto canvasSize = vec2(toGlm(_renderingWidget->size()));;

View file

@ -38,7 +38,7 @@ const float MAGNIFY_MULT = 2.0f;
class CompositorHelper : public QObject, public Dependency {
Q_OBJECT
Q_PROPERTY(float alpha READ getAlpha WRITE setAlpha)
Q_PROPERTY(float alpha READ getAlpha WRITE setAlpha NOTIFY alphaChanged)
Q_PROPERTY(bool reticleOverDesktop READ getReticleOverDesktop WRITE setReticleOverDesktop)
public:
static const uvec2 VIRTUAL_SCREEN_SIZE;
@ -75,12 +75,8 @@ public:
void setModelTransform(const Transform& transform) { _modelTransform = transform; }
const Transform& getModelTransform() const { return _modelTransform; }
void fadeIn();
void fadeOut();
void toggle();
float getAlpha() const { return _alpha; }
void setAlpha(float alpha) { _alpha = alpha; }
void setAlpha(float alpha) { if (alpha != _alpha) { emit alphaChanged(); _alpha = alpha; } }
bool getReticleVisible() const { return _reticleVisible; }
void setReticleVisible(bool visible) { _reticleVisible = visible; }
@ -113,10 +109,11 @@ public:
void setReticleOverDesktop(bool value) { _isOverDesktop = value; }
void setDisplayPlugin(const DisplayPluginPointer& displayPlugin) { _currentDisplayPlugin = displayPlugin; }
void setFrameInfo(uint32_t frame, const glm::mat4& camera) { _currentCamera = camera; _currentFrame = frame; }
void setFrameInfo(uint32_t frame, const glm::mat4& camera) { _currentCamera = camera; }
signals:
void allowMouseCaptureChanged();
void alphaChanged();
protected slots:
void sendFakeMouseEvent();
@ -127,7 +124,6 @@ private:
DisplayPluginPointer _currentDisplayPlugin;
glm::mat4 _currentCamera;
uint32_t _currentFrame { 0 };
QWidget* _renderingWidget{ nullptr };
//// Support for hovering and tooltips
@ -144,16 +140,7 @@ private:
float _textureAspectRatio { VIRTUAL_UI_ASPECT_RATIO };
float _alpha { 1.0f };
float _prevAlpha { 1.0f };
float _fadeInAlpha { true };
float _oculusUIRadius { 1.0f };
quint64 _fadeStarted { 0 };
float _fadeFailsafeEndValue { 1.0f };
void checkFadeFailsafe();
void startFadeFailsafe(float endValue);
int _reticleQuad;
float _hmdUIRadius { 1.0f };
int _previousBorderWidth { -1 };
int _previousBorderHeight { -1 };

View file

@ -215,9 +215,10 @@ OpenGLDisplayPlugin::OpenGLDisplayPlugin() {
}
void OpenGLDisplayPlugin::cleanupForSceneTexture(const gpu::TexturePointer& sceneTexture) {
Lock lock(_mutex);
Q_ASSERT(_sceneTextureToFrameIndexMap.contains(sceneTexture));
_sceneTextureToFrameIndexMap.remove(sceneTexture);
withRenderThreadLock([&] {
Q_ASSERT(_sceneTextureToFrameIndexMap.contains(sceneTexture));
_sceneTextureToFrameIndexMap.remove(sceneTexture);
});
}
@ -275,6 +276,15 @@ bool OpenGLDisplayPlugin::activate() {
_container->makeRenderingContextCurrent();
#endif
auto compositorHelper = DependencyManager::get<CompositorHelper>();
connect(compositorHelper.data(), &CompositorHelper::alphaChanged, [this] {
auto compositorHelper = DependencyManager::get<CompositorHelper>();
auto animation = new QPropertyAnimation(this, "overlayAlpha");
animation->setDuration(200);
animation->setEndValue(compositorHelper->getAlpha());
animation->start();
});
if (isHmd() && (getHmdScreen() >= 0)) {
_container->showDisplayPluginsTools();
}
@ -284,6 +294,9 @@ bool OpenGLDisplayPlugin::activate() {
void OpenGLDisplayPlugin::deactivate() {
auto compositorHelper = DependencyManager::get<CompositorHelper>();
disconnect(compositorHelper.data());
#if THREADED_PRESENT
auto presentThread = DependencyManager::get<PresentThread>();
// Does not return until the GL transition has completeed
@ -410,10 +423,9 @@ void OpenGLDisplayPlugin::submitSceneTexture(uint32_t frameIndex, const gpu::Tex
return;
}
{
Lock lock(_mutex);
withRenderThreadLock([&] {
_sceneTextureToFrameIndexMap[sceneTexture] = frameIndex;
}
});
// Submit it to the presentation thread via escrow
_sceneTextureEscrow.submit(sceneTexture);
@ -447,11 +459,12 @@ void OpenGLDisplayPlugin::updateTextures() {
}
void OpenGLDisplayPlugin::updateFrameData() {
Lock lock(_mutex);
auto previousFrameIndex = _currentPresentFrameIndex;
_currentPresentFrameIndex = _sceneTextureToFrameIndexMap[_currentSceneTexture];
auto skippedCount = (_currentPresentFrameIndex - previousFrameIndex) - 1;
_droppedFrameRate.increment(skippedCount);
withPresentThreadLock([&] {
auto previousFrameIndex = _currentPresentFrameIndex;
_currentPresentFrameIndex = _sceneTextureToFrameIndexMap[_currentSceneTexture];
auto skippedCount = (_currentPresentFrameIndex - previousFrameIndex) - 1;
_droppedFrameRate.increment(skippedCount);
});
}
void OpenGLDisplayPlugin::compositeOverlay() {
@ -460,25 +473,22 @@ void OpenGLDisplayPlugin::compositeOverlay() {
auto compositorHelper = DependencyManager::get<CompositorHelper>();
useProgram(_program);
// set the alpha
Uniform<float>(*_program, _alphaUniform).Set(_compositeOverlayAlpha);
// check the alpha
auto overlayAlpha = compositorHelper->getAlpha();
if (overlayAlpha > 0.0f) {
// set the alpha
Uniform<float>(*_program, _alphaUniform).Set(overlayAlpha);
// Overlay draw
if (isStereo()) {
Uniform<glm::mat4>(*_program, _mvpUniform).Set(mat4());
for_each_eye([&](Eye eye) {
eyeViewport(eye);
drawUnitQuad();
});
} else {
// Overlay draw
Uniform<glm::mat4>(*_program, _mvpUniform).Set(mat4());
// Overlay draw
if (isStereo()) {
Uniform<glm::mat4>(*_program, _mvpUniform).Set(mat4());
for_each_eye([&](Eye eye) {
eyeViewport(eye);
drawUnitQuad();
}
});
} else {
// Overlay draw
Uniform<glm::mat4>(*_program, _mvpUniform).Set(mat4());
drawUnitQuad();
}
// restore the alpha
Uniform<float>(*_program, _alphaUniform).Set(1.0);
}
@ -487,23 +497,19 @@ void OpenGLDisplayPlugin::compositePointer() {
auto compositorHelper = DependencyManager::get<CompositorHelper>();
useProgram(_program);
// check the alpha
auto overlayAlpha = compositorHelper->getAlpha();
if (overlayAlpha > 0.0f) {
// set the alpha
Uniform<float>(*_program, _alphaUniform).Set(overlayAlpha);
Uniform<glm::mat4>(*_program, _mvpUniform).Set(compositorHelper->getReticleTransform(glm::mat4()));
if (isStereo()) {
for_each_eye([&](Eye eye) {
eyeViewport(eye);
drawUnitQuad();
});
} else {
// set the alpha
Uniform<float>(*_program, _alphaUniform).Set(_compositeOverlayAlpha);
Uniform<glm::mat4>(*_program, _mvpUniform).Set(compositorHelper->getReticleTransform(glm::mat4()));
if (isStereo()) {
for_each_eye([&](Eye eye) {
eyeViewport(eye);
drawUnitQuad();
}
});
} else {
drawUnitQuad();
}
Uniform<glm::mat4>(*_program, _mvpUniform).Set(mat4());
// restore the alpha
Uniform<float>(*_program, _alphaUniform).Set(1.0);
}
@ -523,14 +529,14 @@ void OpenGLDisplayPlugin::compositeLayers() {
}
_compositeFramebuffer->Bound(Framebuffer::Target::Draw, [&] {
Context::Viewport(targetRenderSize.x, targetRenderSize.y);
Context::Clear().DepthBuffer();
glBindTexture(GL_TEXTURE_2D, getSceneTextureId());
compositeScene();
auto sceneTextureId = getSceneTextureId();
auto overlayTextureId = getOverlayTextureId();
glBindTexture(GL_TEXTURE_2D, sceneTextureId);
compositeScene();
if (overlayTextureId) {
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glBindTexture(GL_TEXTURE_2D, overlayTextureId);
Context::Enable(Capability::Blend);
Context::BlendFunc(BlendFunction::SrcAlpha, BlendFunction::OneMinusSrcAlpha);
compositeOverlay();
auto compositorHelper = DependencyManager::get<CompositorHelper>();
@ -538,11 +544,16 @@ void OpenGLDisplayPlugin::compositeLayers() {
auto& cursorManager = Cursor::Manager::instance();
const auto& cursorData = _cursorsData[cursorManager.getCursor()->getIcon()];
glBindTexture(GL_TEXTURE_2D, cursorData.texture);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, overlayTextureId);
compositePointer();
glBindTexture(GL_TEXTURE_2D, 0);
glActiveTexture(GL_TEXTURE0);
}
glBindTexture(GL_TEXTURE_2D, 0);
glDisable(GL_BLEND);
Context::Disable(Capability::Blend);
}
compositeExtra();
});
}
@ -580,7 +591,11 @@ float OpenGLDisplayPlugin::newFramePresentRate() const {
}
float OpenGLDisplayPlugin::droppedFrameRate() const {
return _droppedFrameRate.rate();
float result;
withRenderThreadLock([&] {
result = _droppedFrameRate.rate();
});
return result;
}
float OpenGLDisplayPlugin::presentRate() const {
@ -695,3 +710,18 @@ void OpenGLDisplayPlugin::useProgram(const ProgramPtr& program) {
_activeProgram = program;
}
}
void OpenGLDisplayPlugin::assertIsRenderThread() const {
Q_ASSERT(QThread::currentThread() != _presentThread);
}
void OpenGLDisplayPlugin::assertIsPresentThread() const {
Q_ASSERT(QThread::currentThread() == _presentThread);
}
bool OpenGLDisplayPlugin::beginFrameRender(uint32_t frameIndex) {
withRenderThreadLock([&] {
_compositeOverlayAlpha = _overlayAlpha;
});
return Parent::beginFrameRender(frameIndex);
}

View file

@ -24,7 +24,9 @@
#define THREADED_PRESENT 1
class OpenGLDisplayPlugin : public DisplayPlugin {
Q_OBJECT
Q_PROPERTY(float overlayAlpha MEMBER _overlayAlpha)
using Parent = DisplayPlugin;
protected:
using Mutex = std::mutex;
using Lock = std::unique_lock<Mutex>;
@ -61,6 +63,7 @@ public:
float droppedFrameRate() const override;
bool beginFrameRender(uint32_t frameIndex) override;
protected:
#if THREADED_PRESENT
friend class PresentThread;
@ -75,6 +78,7 @@ protected:
virtual void compositeScene();
virtual void compositeOverlay();
virtual void compositePointer();
virtual void compositeExtra() {};
virtual bool hasFocus() const override;
@ -110,12 +114,12 @@ protected:
int32_t _alphaUniform { -1 };
ShapeWrapperPtr _plane;
mutable Mutex _mutex;
RateCounter<> _droppedFrameRate;
RateCounter<> _newFrameRate;
RateCounter<> _presentRate;
QMap<gpu::TexturePointer, uint32_t> _sceneTextureToFrameIndexMap;
uint32_t _currentPresentFrameIndex { 0 };
float _compositeOverlayAlpha{ 1.0f };
gpu::TexturePointer _currentSceneTexture;
gpu::TexturePointer _currentOverlayTexture;
@ -136,10 +140,28 @@ protected:
BasicFramebufferWrapperPtr _compositeFramebuffer;
bool _lockCurrentTexture { false };
void assertIsRenderThread() const;
void assertIsPresentThread() const;
template<typename F>
void withPresentThreadLock(F f) const {
assertIsPresentThread();
Lock lock(_presentMutex);
f();
}
template<typename F>
void withRenderThreadLock(F f) const {
assertIsRenderThread();
Lock lock(_presentMutex);
f();
}
private:
using Parent = DisplayPlugin;
// Any resource shared by the main thread and the presentation thread must
// be serialized through this mutex
mutable Mutex _presentMutex;
ProgramPtr _activeProgram;
float _overlayAlpha{ 1.0f };
};

View file

@ -9,6 +9,7 @@
#include <memory>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtx/intersect.hpp>
#include <QtCore/QLoggingCategory>
#include <QtWidgets/QApplication>
@ -37,7 +38,6 @@ QRect HmdDisplayPlugin::getRecommendedOverlayRect() const {
return CompositorHelper::VIRTUAL_SCREEN_RECOMMENDED_OVERLAY_RECT;
}
bool HmdDisplayPlugin::internalActivate() {
_monoPreview = _container->getBoolSetting("monoPreview", DEFAULT_MONO_VIEW);
@ -197,14 +197,43 @@ static ProgramPtr getReprojectionProgram() {
#endif
static const char * LASER_VS = R"VS(#version 410 core
uniform mat4 mvp = mat4(1);
in vec3 Position;
out vec3 vPosition;
void main() {
gl_Position = mvp * vec4(Position, 1);
vPosition = Position;
}
)VS";
static const char * LASER_FS = R"FS(#version 410 core
uniform vec4 color = vec4(1.0, 1.0, 1.0, 1.0);
in vec3 vPosition;
out vec4 FragColor;
void main() {
FragColor = color;
}
)FS";
void HmdDisplayPlugin::customizeContext() {
Parent::customizeContext();
// Only enable mirroring if we know vsync is disabled
enableVsync(false);
_enablePreview = !isVsyncEnabled();
_sphereSection = loadSphereSection(_program, CompositorHelper::VIRTUAL_UI_TARGET_FOV.y, CompositorHelper::VIRTUAL_UI_ASPECT_RATIO);
compileProgram(_laserProgram, LASER_VS, LASER_FS);
_laserGeometry = loadLaser(_laserProgram);
compileProgram(_reprojectionProgram, REPROJECTION_VS, REPROJECTION_FS);
using namespace oglplus;
REPROJECTION_MATRIX_LOCATION = Uniform<glm::mat3>(*_reprojectionProgram, "reprojection").Location();
INVERSE_PROJECTION_MATRIX_LOCATION = Uniform<glm::mat4>(*_reprojectionProgram, "inverseProjections").Location();
@ -215,6 +244,8 @@ void HmdDisplayPlugin::uncustomizeContext() {
_sphereSection.reset();
_compositeFramebuffer.reset();
_reprojectionProgram.reset();
_laserProgram.reset();
_laserGeometry.reset();
Parent::uncustomizeContext();
}
@ -253,23 +284,20 @@ void HmdDisplayPlugin::compositeScene() {
void HmdDisplayPlugin::compositeOverlay() {
using namespace oglplus;
auto compositorHelper = DependencyManager::get<CompositorHelper>();
glm::mat4 modelMat = compositorHelper->getModelTransform().getMatrix();
// check the alpha
useProgram(_program);
auto overlayAlpha = compositorHelper->getAlpha();
if (overlayAlpha > 0.0f) {
// set the alpha
Uniform<float>(*_program, _alphaUniform).Set(overlayAlpha);
_sphereSection->Use();
for_each_eye([&](Eye eye) {
eyeViewport(eye);
auto modelView = glm::inverse(_currentPresentFrameInfo.presentPose * getEyeToHeadTransform(eye));
auto mvp = _eyeProjections[eye] * modelView;
Uniform<glm::mat4>(*_program, _mvpUniform).Set(mvp);
_sphereSection->Draw();
});
}
// set the alpha
Uniform<float>(*_program, _alphaUniform).Set(_compositeOverlayAlpha);
_sphereSection->Use();
for_each_eye([&](Eye eye) {
eyeViewport(eye);
auto modelView = glm::inverse(_currentPresentFrameInfo.presentPose * getEyeToHeadTransform(eye)) * modelMat;
auto mvp = _eyeProjections[eye] * modelView;
Uniform<glm::mat4>(*_program, _mvpUniform).Set(mvp);
_sphereSection->Draw();
});
// restore the alpha
Uniform<float>(*_program, _alphaUniform).Set(1.0);
}
@ -278,29 +306,27 @@ void HmdDisplayPlugin::compositePointer() {
auto compositorHelper = DependencyManager::get<CompositorHelper>();
// check the alpha
useProgram(_program);
auto overlayAlpha = compositorHelper->getAlpha();
if (overlayAlpha > 0.0f) {
// set the alpha
Uniform<float>(*_program, _alphaUniform).Set(overlayAlpha);
// set the alpha
Uniform<float>(*_program, _alphaUniform).Set(_compositeOverlayAlpha);
// Mouse pointer
_plane->Use();
// Reconstruct the headpose from the eye poses
auto headPosition = vec3(_currentPresentFrameInfo.presentPose[3]);
for_each_eye([&](Eye eye) {
eyeViewport(eye);
auto eyePose = _currentPresentFrameInfo.presentPose * getEyeToHeadTransform(eye);
auto reticleTransform = compositorHelper->getReticleTransform(eyePose, headPosition);
auto mvp = _eyeProjections[eye] * reticleTransform;
Uniform<glm::mat4>(*_program, _mvpUniform).Set(mvp);
_plane->Draw();
});
}
// Mouse pointer
_plane->Use();
// Reconstruct the headpose from the eye poses
auto headPosition = vec3(_currentPresentFrameInfo.presentPose[3]);
for_each_eye([&](Eye eye) {
eyeViewport(eye);
auto eyePose = _currentPresentFrameInfo.presentPose * getEyeToHeadTransform(eye);
auto reticleTransform = compositorHelper->getReticleTransform(eyePose, headPosition);
auto mvp = _eyeProjections[eye] * reticleTransform;
Uniform<glm::mat4>(*_program, _mvpUniform).Set(mvp);
_plane->Draw();
});
// restore the alpha
Uniform<float>(*_program, _alphaUniform).Set(1.0);
}
void HmdDisplayPlugin::internalPresent() {
PROFILE_RANGE_EX(__FUNCTION__, 0xff00ff00, (uint64_t)presentCount())
@ -357,22 +383,117 @@ void HmdDisplayPlugin::setEyeRenderPose(uint32_t frameIndex, Eye eye, const glm:
void HmdDisplayPlugin::updateFrameData() {
// Check if we have old frame data to discard
{
Lock lock(_mutex);
withPresentThreadLock([&] {
auto itr = _frameInfos.find(_currentPresentFrameIndex);
if (itr != _frameInfos.end()) {
_frameInfos.erase(itr);
}
}
});
Parent::updateFrameData();
{
Lock lock(_mutex);
withPresentThreadLock([&] {
_currentPresentFrameInfo = _frameInfos[_currentPresentFrameIndex];
}
});
}
glm::mat4 HmdDisplayPlugin::getHeadPose() const {
return _currentRenderFrameInfo.renderPose;
}
bool HmdDisplayPlugin::setHandLaser(uint32_t hands, HandLaserMode mode, const vec4& color, const vec3& direction) {
HandLaserInfo info;
info.mode = mode;
info.color = color;
info.direction = direction;
withRenderThreadLock([&] {
if (hands & Hand::LeftHand) {
_handLasers[0] = info;
}
if (hands & Hand::RightHand) {
_handLasers[1] = info;
}
});
// FIXME defer to a child class plugin to determine if hand lasers are actually
// available based on the presence or absence of hand controllers
return true;
}
void HmdDisplayPlugin::compositeExtra() {
const int NUMBER_OF_HANDS = 2;
std::array<HandLaserInfo, NUMBER_OF_HANDS> handLasers;
std::array<mat4, NUMBER_OF_HANDS> renderHandPoses;
Transform uiModelTransform;
withPresentThreadLock([&] {
handLasers = _handLasers;
renderHandPoses = _handPoses;
uiModelTransform = _uiModelTransform;
});
// If neither hand laser is activated, exit
if (!handLasers[0].valid() && !handLasers[1].valid()) {
return;
}
static const glm::mat4 identity;
if (renderHandPoses[0] == identity && renderHandPoses[1] == identity) {
return;
}
// Render hand lasers
using namespace oglplus;
useProgram(_laserProgram);
_laserGeometry->Use();
std::array<mat4, NUMBER_OF_HANDS> handLaserModelMatrices;
for (int i = 0; i < NUMBER_OF_HANDS; ++i) {
if (renderHandPoses[i] == identity) {
continue;
}
const auto& handLaser = handLasers[i];
if (!handLaser.valid()) {
continue;
}
const auto& laserDirection = handLaser.direction;
auto model = renderHandPoses[i];
auto castDirection = glm::quat_cast(model) * laserDirection;
if (glm::abs(glm::length2(castDirection) - 1.0f) > EPSILON) {
castDirection = glm::normalize(castDirection);
}
// FIXME fetch the actual UI radius from... somewhere?
float uiRadius = 1.0f;
// Find the intersection of the laser with he UI and use it to scale the model matrix
float distance;
if (!glm::intersectRaySphere(vec3(renderHandPoses[i][3]), castDirection, uiModelTransform.getTranslation(), uiRadius * uiRadius, distance)) {
continue;
}
// Make sure we rotate to match the desired laser direction
if (laserDirection != Vectors::UNIT_NEG_Z) {
auto rotation = glm::rotation(Vectors::UNIT_NEG_Z, laserDirection);
model = model * glm::mat4_cast(rotation);
}
model = glm::scale(model, vec3(distance));
handLaserModelMatrices[i] = model;
}
for_each_eye([&](Eye eye) {
eyeViewport(eye);
auto eyePose = _currentPresentFrameInfo.presentPose * getEyeToHeadTransform(eye);
auto view = glm::inverse(eyePose);
const auto& projection = _eyeProjections[eye];
for (int i = 0; i < NUMBER_OF_HANDS; ++i) {
if (handLaserModelMatrices[i] == identity) {
continue;
}
Uniform<glm::mat4>(*_laserProgram, "mvp").Set(projection * view * handLaserModelMatrices[i]);
Uniform<glm::vec4>(*_laserProgram, "color").Set(handLasers[i].color);
_laserGeometry->Draw();
// TODO render some kind of visual indicator at the intersection point with the UI.
}
});
}

View file

@ -10,6 +10,7 @@
#include <ThreadSafeValueCache.h>
#include <QtGlobal>
#include <Transform.h>
#include "../OpenGLDisplayPlugin.h"
@ -30,7 +31,7 @@ public:
virtual glm::mat4 getHeadPose() const override;
bool setHandLaser(uint32_t hands, HandLaserMode mode, const vec4& color, const vec3& direction) override;
protected:
virtual void hmdPresent() = 0;
@ -46,7 +47,22 @@ protected:
void customizeContext() override;
void uncustomizeContext() override;
void updateFrameData() override;
void compositeExtra() override;
struct HandLaserInfo {
HandLaserMode mode { HandLaserMode::None };
vec4 color { 1.0f };
vec3 direction { 0, 0, -1 };
// Is this hand laser info suitable for drawing?
bool valid() const {
return (mode != HandLaserMode::None && color.a > 0.0f && direction != vec3());
}
};
Transform _uiModelTransform;
std::array<HandLaserInfo, 2> _handLasers;
std::array<glm::mat4, 2> _handPoses;
std::array<glm::mat4, 2> _eyeOffsets;
std::array<glm::mat4, 2> _eyeProjections;
std::array<glm::mat4, 2> _eyeInverseProjections;
@ -75,5 +91,7 @@ private:
bool _enableReprojection { true };
ShapeWrapperPtr _sphereSection;
ProgramPtr _reprojectionProgram;
ProgramPtr _laserProgram;
ShapeWrapperPtr _laserGeometry;
};

View file

@ -396,6 +396,8 @@ void OffscreenQmlSurface::create(QOpenGLContext* shareContext) {
_renderer->_renderControl->_renderWindow = _proxyWindow;
connect(_renderer->_quickWindow, &QQuickWindow::focusObjectChanged, this, &OffscreenQmlSurface::onFocusObjectChanged);
// Create a QML engine.
_qmlEngine = new QQmlEngine;
if (!_qmlEngine->incubationController()) {
@ -742,3 +744,21 @@ QVariant OffscreenQmlSurface::returnFromUiThread(std::function<QVariant()> funct
return function();
}
void OffscreenQmlSurface::onFocusObjectChanged(QObject* object) {
if (!object) {
setFocusText(false);
return;
}
QInputMethodQueryEvent query(Qt::ImEnabled);
qApp->sendEvent(object, &query);
setFocusText(query.value(Qt::ImEnabled).toBool());
}
void OffscreenQmlSurface::setFocusText(bool newFocusText) {
if (newFocusText != _focusText) {
_focusText = newFocusText;
emit focusTextChanged(_focusText);
}
}

View file

@ -30,7 +30,7 @@ class OffscreenQmlRenderThread;
class OffscreenQmlSurface : public QObject {
Q_OBJECT
Q_PROPERTY(bool focusText READ isFocusText NOTIFY focusTextChanged)
public:
OffscreenQmlSurface();
virtual ~OffscreenQmlSurface();
@ -55,6 +55,7 @@ public:
_mouseTranslator = mouseTranslator;
}
bool isFocusText() const { return _focusText; }
void pause();
void resume();
bool isPaused() const;
@ -70,6 +71,8 @@ public:
signals:
void textureUpdated(unsigned int texture);
void focusObjectChanged(QObject* newFocus);
void focusTextChanged(bool focusText);
public slots:
void requestUpdate();
@ -78,6 +81,7 @@ public slots:
protected:
bool filterEnabled(QObject* originalDestination, QEvent* event) const;
void setFocusText(bool newFocusText);
private:
QObject* finishQmlLoad(std::function<void(QQmlContext*, QObject*)> f);
@ -85,6 +89,7 @@ private:
private slots:
void updateQuick();
void onFocusObjectChanged(QObject* newFocus);
private:
friend class OffscreenQmlRenderThread;
@ -97,6 +102,7 @@ private:
bool _render{ false };
bool _polish{ true };
bool _paused{ true };
bool _focusText { false };
uint8_t _maxFps{ 60 };
MouseTranslator _mouseTranslator{ [](const QPointF& p) { return p.toPoint(); } };
QWindow* _proxyWindow { nullptr };

View file

@ -45,9 +45,11 @@ in vec2 vTexCoord;
out vec4 FragColor;
void main() {
FragColor = texture(sampler, vTexCoord);
FragColor.a *= alpha;
if (FragColor.a <= 0.0) {
discard;
}
}
)FS";
@ -359,6 +361,94 @@ ShapeWrapperPtr loadSphereSection(ProgramPtr program, float fov, float aspect, i
);
}
namespace oglplus {
namespace shapes {
class Laser : public DrawingInstructionWriter, public DrawMode {
public:
using IndexArray = std::vector<GLuint>;
using PosArray = std::vector<float>;
/// The type of the index container returned by Indices()
// vertex positions
PosArray _pos_data;
IndexArray _idx_data;
unsigned int _prim_count { 0 };
public:
Laser() {
int vertices = 2;
_pos_data.resize(vertices * 3);
_pos_data[0] = 0;
_pos_data[1] = 0;
_pos_data[2] = 0;
_pos_data[3] = 0;
_pos_data[4] = 0;
_pos_data[5] = -1;
_idx_data.push_back(0);
_idx_data.push_back(1);
_prim_count = 1;
}
/// Returns the winding direction of faces
FaceOrientation FaceWinding(void) const {
return FaceOrientation::CCW;
}
/// Queries the bounding sphere coordinates and dimensions
template <typename T>
void BoundingSphere(Sphere<T>& bounding_sphere) const {
bounding_sphere = Sphere<T>(0, 0, -0.5, 0.5);
}
typedef GLuint(Laser::*VertexAttribFunc)(std::vector<GLfloat>&) const;
/// Makes the vertex positions and returns the number of values per vertex
template <typename T>
GLuint Positions(std::vector<T>& dest) const {
dest.clear();
dest.insert(dest.begin(), _pos_data.begin(), _pos_data.end());
return 3;
}
typedef VertexAttribsInfo<
Laser,
std::tuple<VertexPositionsTag>
> VertexAttribs;
/// Returns element indices that are used with the drawing instructions
const IndexArray & Indices(Default = Default()) const {
return _idx_data;
}
/// Returns the instructions for rendering of faces
DrawingInstructions Instructions(PrimitiveType primitive) const {
DrawingInstructions instr = MakeInstructions();
DrawOperation operation;
operation.method = DrawOperation::Method::DrawElements;
operation.mode = primitive;
operation.first = 0;
operation.count = _prim_count * 3;
operation.restart_index = DrawOperation::NoRestartIndex();
operation.phase = 0;
AddInstruction(instr, operation);
return instr;
}
/// Returns the instructions for rendering of faces
DrawingInstructions Instructions(Default = Default()) const {
return Instructions(PrimitiveType::Lines);
}
};
}
}
ShapeWrapperPtr loadLaser(const ProgramPtr& program) {
return std::make_shared<shapes::ShapeWrapper>(shapes::ShapeWrapper("Position", shapes::Laser(), *program));
}
void TextureRecycler::setSize(const uvec2& size) {
if (size == _size) {
return;

View file

@ -64,8 +64,9 @@ ProgramPtr loadCubemapShader();
void compileProgram(ProgramPtr & result, const std::string& vs, const std::string& fs);
ShapeWrapperPtr loadSkybox(ProgramPtr program);
ShapeWrapperPtr loadPlane(ProgramPtr program, float aspect = 1.0f);
ShapeWrapperPtr loadSphereSection(ProgramPtr program, float fov = PI / 3.0f * 2.0f, float aspect = 16.0f / 9.0f, int slices = 32, int stacks = 32);
ShapeWrapperPtr loadSphereSection(ProgramPtr program, float fov = PI / 3.0f * 2.0f, float aspect = 16.0f / 9.0f, int slices = 128, int stacks = 128);
ShapeWrapperPtr loadLaser(const ProgramPtr& program);
// A basic wrapper for constructing a framebuffer with a renderbuffer
// for the depth attachment and an undefined type for the color attachement

View file

@ -16,16 +16,18 @@
#include <QtCore/QSize>
#include <QtCore/QPoint>
#include <QtCore/QElapsedTimer>
class QImage;
#include <GLMHelpers.h>
#include <RegisteredMetaTypes.h>
#include <shared/Bilateral.h>
#include "Plugin.h"
class QImage;
enum Eye {
Left,
Right
Left = (int)bilateral::Side::Left,
Right = (int)bilateral::Side::Right
};
/*
@ -56,7 +58,73 @@ namespace gpu {
using TexturePointer = std::shared_ptr<Texture>;
}
class DisplayPlugin : public Plugin {
// Stereo display functionality
// TODO move out of this file don't derive DisplayPlugin from this. Instead use dynamic casting when
// displayPlugin->isStereo returns true
class StereoDisplay {
public:
// Stereo specific methods
virtual glm::mat4 getEyeProjection(Eye eye, const glm::mat4& baseProjection) const {
return baseProjection;
}
virtual glm::mat4 getCullingProjection(const glm::mat4& baseProjection) const {
return baseProjection;
}
virtual float getIPD() const { return AVERAGE_HUMAN_IPD; }
};
// HMD display functionality
// TODO move out of this file don't derive DisplayPlugin from this. Instead use dynamic casting when
// displayPlugin->isHmd returns true
class HmdDisplay : public StereoDisplay {
public:
// HMD specific methods
// TODO move these into another class?
virtual glm::mat4 getEyeToHeadTransform(Eye eye) const {
static const glm::mat4 transform; return transform;
}
// returns a copy of the most recent head pose, computed via updateHeadPose
virtual glm::mat4 getHeadPose() const {
return glm::mat4();
}
// Needed for timewarp style features
virtual void setEyeRenderPose(uint32_t frameIndex, Eye eye, const glm::mat4& pose) {
// NOOP
}
virtual void abandonCalibration() {}
virtual void resetSensors() {}
enum Hand {
LeftHand = 0x01,
RightHand = 0x02,
};
enum class HandLaserMode {
None, // Render no hand lasers
Overlay, // Render hand lasers only if they intersect with the UI layer, and stop at the UI layer
};
virtual bool setHandLaser(
uint32_t hands, // Bits from the Hand enum
HandLaserMode mode, // Mode in which to render
const vec4& color = vec4(1), // The color of the rendered laser
const vec3& direction = vec3(0, 0, -1) // The direction in which to render the hand lasers
) {
return false;
}
virtual bool suppressKeyboard() { return false; }
virtual void unsuppressKeyboard() {};
virtual bool isKeyboardVisible() { return false; }
};
class DisplayPlugin : public Plugin, public HmdDisplay {
Q_OBJECT
using Parent = Plugin;
public:
@ -115,42 +183,12 @@ public:
return QRect(0, 0, recommendedSize.x, recommendedSize.y);
}
// Stereo specific methods
virtual glm::mat4 getEyeProjection(Eye eye, const glm::mat4& baseProjection) const {
return baseProjection;
}
virtual glm::mat4 getCullingProjection(const glm::mat4& baseProjection) const {
return baseProjection;
}
// Fetch the most recently displayed image as a QImage
virtual QImage getScreenshot() const = 0;
// HMD specific methods
// TODO move these into another class?
virtual glm::mat4 getEyeToHeadTransform(Eye eye) const {
static const glm::mat4 transform; return transform;
}
// will query the underlying hmd api to compute the most recent head pose
virtual bool beginFrameRender(uint32_t frameIndex) { return true; }
// returns a copy of the most recent head pose, computed via updateHeadPose
virtual glm::mat4 getHeadPose() const {
return glm::mat4();
}
// Needed for timewarp style features
virtual void setEyeRenderPose(uint32_t frameIndex, Eye eye, const glm::mat4& pose) {
// NOOP
}
virtual float getIPD() const { return AVERAGE_HUMAN_IPD; }
virtual void abandonCalibration() {}
virtual void resetSensors() {}
virtual float devicePixelRatio() { return 1.0f; }
// Rate at which we present to the display device
virtual float presentRate() const { return -1.0f; }
@ -158,6 +196,7 @@ public:
virtual float newFramePresentRate() const { return -1.0f; }
// Rate at which rendered frames are being skipped
virtual float droppedFrameRate() const { return -1.0f; }
uint32_t presentCount() const { return _presentedFrameIndex; }
// Time since last call to incrementPresentCount (only valid if DEBUG_PAINT_DELAY is defined)
int64_t getPaintDelayUsecs() const;
@ -166,6 +205,7 @@ public:
static const QString& MENU_PATH();
signals:
void recommendedFramebufferSizeChanged(const QSize & size);

View file

@ -0,0 +1,49 @@
//
// Created by Bradley Austin Davis 2015/10/09
// Copyright 2015 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
//
#pragma once
namespace bilateral {
enum class Side {
Left = 0,
Right = 1
};
using Indices = Side;
enum class Bits {
Left = 0x01,
Right = 0x02
};
inline uint8_t bit(Side side) {
switch (side) {
case Side::Left:
return 0x01;
case Side::Right:
return 0x02;
}
return UINT8_MAX;
}
inline uint8_t index(Side side) {
switch (side) {
case Side::Left:
return 0;
case Side::Right:
return 1;
}
return UINT8_MAX;
}
template <typename F>
void for_each_side(F f) {
f(Side::Left);
f(Side::Right);
}
}

View file

@ -22,10 +22,6 @@ QString ErrorDialog::text() const {
return _text;
}
void ErrorDialog::setVisible(bool v) {
OffscreenQmlDialog::setVisible(v);
}
void ErrorDialog::setText(const QString& arg) {
if (arg != _text) {
_text = arg;

View file

@ -30,7 +30,6 @@ public:
QString text() const;
public slots:
virtual void setVisible(bool v);
void setText(const QString& arg);
signals:

View file

@ -17,7 +17,7 @@ OffscreenQmlDialog::~OffscreenQmlDialog() {
}
void OffscreenQmlDialog::hide() {
static_cast<QQuickItem*>(parent())->setVisible(false);
parent()->setProperty(OFFSCREEN_VISIBILITY_PROPERTY, false);
}
QString OffscreenQmlDialog::title() const {

View file

@ -121,32 +121,28 @@ void OffscreenUi::show(const QUrl& url, const QString& name, std::function<void(
load(url, f);
item = getRootItem()->findChild<QQuickItem*>(name);
}
if (item) {
item->setVisible(true);
QQmlProperty(item, OFFSCREEN_VISIBILITY_PROPERTY).write(true);
}
}
void OffscreenUi::toggle(const QUrl& url, const QString& name, std::function<void(QQmlContext*, QObject*)> f) {
QQuickItem* item = getRootItem()->findChild<QQuickItem*>(name);
// Already loaded?
if (item) {
emit showDesktop();
item->setVisible(!item->isVisible());
if (!item) {
show(url, name, f);
return;
}
load(url, f);
item = getRootItem()->findChild<QQuickItem*>(name);
if (item && !item->isVisible()) {
emit showDesktop();
item->setVisible(true);
}
// Already loaded, so just flip the bit
QQmlProperty shownProperty(item, OFFSCREEN_VISIBILITY_PROPERTY);
shownProperty.write(!shownProperty.read().toBool());
}
void OffscreenUi::hide(const QString& name) {
QQuickItem* item = getRootItem()->findChild<QQuickItem*>(name);
if (item) {
item->setVisible(false);
QQmlProperty(item, OFFSCREEN_VISIBILITY_PROPERTY).write(false);
}
}
@ -345,6 +341,20 @@ QVariant OffscreenUi::inputDialog(const Icon icon, const QString& title, const Q
return waitForInputDialogResult(createInputDialog(icon, title, label, current));
}
void OffscreenUi::togglePinned() {
bool invokeResult = QMetaObject::invokeMethod(_desktop, "togglePinned");
if (!invokeResult) {
qWarning() << "Failed to toggle window visibility";
}
}
void OffscreenUi::setPinned(bool pinned) {
bool invokeResult = QMetaObject::invokeMethod(_desktop, "setPinned", Q_ARG(QVariant, pinned));
if (!invokeResult) {
qWarning() << "Failed to set window visibility";
}
}
void OffscreenUi::addMenuInitializer(std::function<void(VrMenu*)> f) {
if (!_vrMenu) {
_queuedMenuInitializers.push_back(f);

View file

@ -28,6 +28,8 @@
class VrMenu;
#define OFFSCREEN_VISIBILITY_PROPERTY "shown"
class OffscreenUi : public OffscreenQmlSurface, public Dependency {
Q_OBJECT
@ -44,6 +46,13 @@ public:
void setNavigationFocused(bool focused);
void unfocusWindows();
void toggleMenu(const QPoint& screenCoordinates);
// Setting pinned to true will hide all overlay elements on the desktop that don't have a pinned flag
void setPinned(bool pinned = true);
void togglePinned();
bool eventFilter(QObject* originalDestination, QEvent* event) override;
void addMenuInitializer(std::function<void(VrMenu*)> f);

View file

@ -163,8 +163,7 @@ void QmlWindowClass::setVisible(bool visible) {
QMetaObject::invokeMethod(targetWindow, "showTabForUrl", Qt::QueuedConnection, Q_ARG(QVariant, _source), Q_ARG(QVariant, visible));
} else {
DependencyManager::get<OffscreenUi>()->executeOnUiThread([=] {
targetWindow->setVisible(visible);
//emit visibilityChanged(visible);
targetWindow->setProperty(OFFSCREEN_VISIBILITY_PROPERTY, visible);
});
}
}

View file

@ -47,10 +47,6 @@ void Tooltip::setImageURL(const QString& imageURL) {
}
}
void Tooltip::setVisible(bool visible) {
QQuickItem::setVisible(visible);
}
QString Tooltip::showTip(const QString& title, const QString& description) {
const QString newTipId = QUuid().createUuid().toString();

View file

@ -39,8 +39,6 @@ public:
static void closeTip(const QString& tipId);
public slots:
virtual void setVisible(bool v);
void setTitle(const QString& title);
void setDescription(const QString& description);
void setImageURL(const QString& imageURL);

View file

@ -8,6 +8,8 @@
#include "OculusBaseDisplayPlugin.h"
#include <ViewFrustum.h>
#include <controllers/Pose.h>
#include <display-plugins/CompositorHelper.h>
#include "OculusHelpers.h"
@ -24,9 +26,26 @@ bool OculusBaseDisplayPlugin::beginFrameRender(uint32_t frameIndex) {
auto trackingState = ovr_GetTrackingState(_session, _currentRenderFrameInfo.predictedDisplayTime, ovrTrue);
_currentRenderFrameInfo.renderPose = toGlm(trackingState.HeadPose.ThePose);
_currentRenderFrameInfo.presentPose = _currentRenderFrameInfo.renderPose;
Lock lock(_mutex);
_frameInfos[frameIndex] = _currentRenderFrameInfo;
return true;
std::array<glm::mat4, 2> handPoses;
// Make controller poses available to the presentation thread
ovr_for_each_hand([&](ovrHandType hand) {
static const auto REQUIRED_HAND_STATUS = ovrStatus_OrientationTracked & ovrStatus_PositionTracked;
if (REQUIRED_HAND_STATUS != (trackingState.HandStatusFlags[hand] & REQUIRED_HAND_STATUS)) {
return;
}
auto correctedPose = ovrControllerPoseToHandPose(hand, trackingState.HandPoses[hand]);
static const glm::quat HAND_TO_LASER_ROTATION = glm::rotation(Vectors::UNIT_Z, Vectors::UNIT_NEG_Y);
handPoses[hand] = glm::translate(glm::mat4(), correctedPose.translation) * glm::mat4_cast(correctedPose.rotation * HAND_TO_LASER_ROTATION);
});
withRenderThreadLock([&] {
_uiModelTransform = DependencyManager::get<CompositorHelper>()->getModelTransform();
_handPoses = handPoses;
_frameInfos[frameIndex] = _currentRenderFrameInfo;
});
return Parent::beginFrameRender(frameIndex);
}
bool OculusBaseDisplayPlugin::isSupported() const {

View file

@ -243,91 +243,13 @@ void OculusControllerManager::TouchDevice::focusOutEvent() {
void OculusControllerManager::TouchDevice::handlePose(float deltaTime,
const controller::InputCalibrationData& inputCalibrationData, ovrHandType hand,
const ovrPoseStatef& handPose) {
// When the sensor-to-world rotation is identity the coordinate axes look like this:
//
// user
// forward
// -z
// |
// y| user
// y o----x right
// o-----x user
// | up
// |
// z
//
// Rift
// From ABOVE the hand canonical axes looks like this:
//
// | | | | y | | | |
// | | | | | | | | |
// | | | | |
// |left | / x---- + \ |right|
// | _/ z \_ |
// | | | |
// | | | |
//
// So when the user is in Rift space facing the -zAxis with hands outstretched and palms down
// the rotation to align the Touch axes with those of the hands is:
//
// touchToHand = halfTurnAboutY * quaterTurnAboutX
// Due to how the Touch controllers fit into the palm there is an offset that is different for each hand.
// You can think of this offset as the inverse of the measured rotation when the hands are posed, such that
// the combination (measurement * offset) is identity at this orientation.
//
// Qoffset = glm::inverse(deltaRotation when hand is posed fingers forward, palm down)
//
// An approximate offset for the Touch can be obtained by inspection:
//
// Qoffset = glm::inverse(glm::angleAxis(sign * PI/2.0f, zAxis) * glm::angleAxis(PI/4.0f, xAxis))
//
// So the full equation is:
//
// Q = combinedMeasurement * touchToHand
//
// Q = (deltaQ * QOffset) * (yFlip * quarterTurnAboutX)
//
// Q = (deltaQ * inverse(deltaQForAlignedHand)) * (yFlip * quarterTurnAboutX)
auto poseId = hand == ovrHand_Left ? controller::LEFT_HAND : controller::RIGHT_HAND;
auto& pose = _poseStateMap[poseId];
static const glm::quat yFlip = glm::angleAxis(PI, Vectors::UNIT_Y);
static const glm::quat quarterX = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_X);
static const glm::quat touchToHand = yFlip * quarterX;
static const glm::quat leftQuarterZ = glm::angleAxis(-PI_OVER_TWO, Vectors::UNIT_Z);
static const glm::quat rightQuarterZ = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_Z);
static const glm::quat eighthX = glm::angleAxis(PI / 4.0f, Vectors::UNIT_X);
static const glm::quat leftRotationOffset = glm::inverse(leftQuarterZ * eighthX) * touchToHand;
static const glm::quat rightRotationOffset = glm::inverse(rightQuarterZ * eighthX) * touchToHand;
static const float CONTROLLER_LENGTH_OFFSET = 0.0762f; // three inches
static const glm::vec3 CONTROLLER_OFFSET = glm::vec3(CONTROLLER_LENGTH_OFFSET / 2.0f,
CONTROLLER_LENGTH_OFFSET / 2.0f,
CONTROLLER_LENGTH_OFFSET * 2.0f);
static const glm::vec3 leftTranslationOffset = glm::vec3(-1.0f, 1.0f, 1.0f) * CONTROLLER_OFFSET;
static const glm::vec3 rightTranslationOffset = CONTROLLER_OFFSET;
auto translationOffset = (hand == ovrHand_Left ? leftTranslationOffset : rightTranslationOffset);
auto rotationOffset = (hand == ovrHand_Left ? leftRotationOffset : rightRotationOffset);
glm::quat rotation = toGlm(handPose.ThePose.Orientation);
pose.translation = toGlm(handPose.ThePose.Position);
pose.translation += rotation * translationOffset;
pose.rotation = rotation * rotationOffset;
pose.angularVelocity = toGlm(handPose.AngularVelocity);
pose.velocity = toGlm(handPose.LinearVelocity);
pose.valid = true;
pose = ovrControllerPoseToHandPose(hand, handPose);
// transform into avatar frame
glm::mat4 controllerToAvatar = glm::inverse(inputCalibrationData.avatarMat) * inputCalibrationData.sensorToWorldMat;
pose = pose.transform(controllerToAvatar);
}
bool OculusControllerManager::TouchDevice::triggerHapticPulse(float strength, float duration, controller::Hand hand) {

View file

@ -14,6 +14,10 @@
#include <QtCore/QLoggingCategory>
#include <QtCore/QFile>
#include <QtCore/QDir>
#include <QtCore/QProcessEnvironment>
#include <controllers/Input.h>
#include <controllers/Pose.h>
using Mutex = std::mutex;
using Lock = std::unique_lock<Mutex>;
@ -50,6 +54,13 @@ bool oculusAvailable() {
static std::once_flag once;
static bool result { false };
std::call_once(once, [&] {
static const QString DEBUG_FLAG("HIFI_DEBUG_OPENVR");
static bool enableDebugOpenVR = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG);
if (enableDebugOpenVR) {
return;
}
ovrDetectResult detect = ovr_Detect(0);
if (!detect.IsOculusServiceRunning || !detect.IsOculusHMDConnected) {
return;
@ -191,3 +202,88 @@ void SwapFramebufferWrapper::onBind(oglplus::Framebuffer::Target target) {
void SwapFramebufferWrapper::onUnbind(oglplus::Framebuffer::Target target) {
glFramebufferTexture2D(toEnum(target), GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
}
controller::Pose ovrControllerPoseToHandPose(
ovrHandType hand,
const ovrPoseStatef& handPose) {
// When the sensor-to-world rotation is identity the coordinate axes look like this:
//
// user
// forward
// -z
// |
// y| user
// y o----x right
// o-----x user
// | up
// |
// z
//
// Rift
// From ABOVE the hand canonical axes looks like this:
//
// | | | | y | | | |
// | | | | | | | | |
// | | | | |
// |left | / x---- + \ |right|
// | _/ z \_ |
// | | | |
// | | | |
//
// So when the user is in Rift space facing the -zAxis with hands outstretched and palms down
// the rotation to align the Touch axes with those of the hands is:
//
// touchToHand = halfTurnAboutY * quaterTurnAboutX
// Due to how the Touch controllers fit into the palm there is an offset that is different for each hand.
// You can think of this offset as the inverse of the measured rotation when the hands are posed, such that
// the combination (measurement * offset) is identity at this orientation.
//
// Qoffset = glm::inverse(deltaRotation when hand is posed fingers forward, palm down)
//
// An approximate offset for the Touch can be obtained by inspection:
//
// Qoffset = glm::inverse(glm::angleAxis(sign * PI/2.0f, zAxis) * glm::angleAxis(PI/4.0f, xAxis))
//
// So the full equation is:
//
// Q = combinedMeasurement * touchToHand
//
// Q = (deltaQ * QOffset) * (yFlip * quarterTurnAboutX)
//
// Q = (deltaQ * inverse(deltaQForAlignedHand)) * (yFlip * quarterTurnAboutX)
static const glm::quat yFlip = glm::angleAxis(PI, Vectors::UNIT_Y);
static const glm::quat quarterX = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_X);
static const glm::quat touchToHand = yFlip * quarterX;
static const glm::quat leftQuarterZ = glm::angleAxis(-PI_OVER_TWO, Vectors::UNIT_Z);
static const glm::quat rightQuarterZ = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_Z);
static const glm::quat eighthX = glm::angleAxis(PI / 4.0f, Vectors::UNIT_X);
static const glm::quat leftRotationOffset = glm::inverse(leftQuarterZ * eighthX) * touchToHand;
static const glm::quat rightRotationOffset = glm::inverse(rightQuarterZ * eighthX) * touchToHand;
static const float CONTROLLER_LENGTH_OFFSET = 0.0762f; // three inches
static const glm::vec3 CONTROLLER_OFFSET = glm::vec3(CONTROLLER_LENGTH_OFFSET / 2.0f,
CONTROLLER_LENGTH_OFFSET / 2.0f,
CONTROLLER_LENGTH_OFFSET * 2.0f);
static const glm::vec3 leftTranslationOffset = glm::vec3(-1.0f, 1.0f, 1.0f) * CONTROLLER_OFFSET;
static const glm::vec3 rightTranslationOffset = CONTROLLER_OFFSET;
auto translationOffset = (hand == ovrHand_Left ? leftTranslationOffset : rightTranslationOffset);
auto rotationOffset = (hand == ovrHand_Left ? leftRotationOffset : rightRotationOffset);
glm::quat rotation = toGlm(handPose.ThePose.Orientation);
controller::Pose pose;
pose.translation = toGlm(handPose.ThePose.Position);
pose.translation += rotation * translationOffset;
pose.rotation = rotation * rotationOffset;
pose.angularVelocity = toGlm(handPose.AngularVelocity);
pose.velocity = toGlm(handPose.LinearVelocity);
pose.valid = true;
return pose;
}

View file

@ -13,6 +13,7 @@
#include <glm/gtc/matrix_transform.hpp>
#include <gl/OglplusHelpers.h>
#include <controllers/Forward.h>
void logWarning(const char* what);
void logFatal(const char* what);
@ -128,3 +129,7 @@ protected:
private:
ovrSession _session;
};
controller::Pose ovrControllerPoseToHandPose(
ovrHandType hand,
const ovrPoseStatef& handPose);

View file

@ -47,13 +47,15 @@ void OculusLegacyDisplayPlugin::resetSensors() {
}
bool OculusLegacyDisplayPlugin::beginFrameRender(uint32_t frameIndex) {
_currentRenderFrameInfo = FrameInfo();
_currentRenderFrameInfo.predictedDisplayTime = _currentRenderFrameInfo.sensorSampleTime = ovr_GetTimeInSeconds();
_trackingState = ovrHmd_GetTrackingState(_hmd, _currentRenderFrameInfo.predictedDisplayTime);
_currentRenderFrameInfo.rawRenderPose = _currentRenderFrameInfo.renderPose = toGlm(_trackingState.HeadPose.ThePose);
Lock lock(_mutex);
_frameInfos[frameIndex] = _currentRenderFrameInfo;
return true;
withRenderThreadLock([&]{
_frameInfos[frameIndex] = _currentRenderFrameInfo;
});
return Parent::beginFrameRender(frameIndex);
}
bool OculusLegacyDisplayPlugin::isSupported() const {

View file

@ -18,9 +18,11 @@
#include <GLMHelpers.h>
#include <gl/GlWindow.h>
#include <controllers/Pose.h>
#include <PerfStat.h>
#include <ui-plugins/PluginContainer.h>
#include <ViewFrustum.h>
#include <display-plugins/CompositorHelper.h>
#include <shared/NsightHelpers.h>
#include "OpenVrHelpers.h"
@ -29,13 +31,16 @@ Q_DECLARE_LOGGING_CATEGORY(displayplugins)
const QString OpenVrDisplayPlugin::NAME("OpenVR (Vive)");
const QString StandingHMDSensorMode = "Standing HMD Sensor Mode"; // this probably shouldn't be hardcoded here
static vr::IVRCompositor* _compositor{ nullptr };
static vr::IVRCompositor* _compositor { nullptr };
vr::TrackedDevicePose_t _trackedDevicePose[vr::k_unMaxTrackedDeviceCount];
mat4 _trackedDevicePoseMat4[vr::k_unMaxTrackedDeviceCount];
vec3 _trackedDeviceLinearVelocities[vr::k_unMaxTrackedDeviceCount];
vec3 _trackedDeviceAngularVelocities[vr::k_unMaxTrackedDeviceCount];
static mat4 _sensorResetMat;
static std::array<vr::Hmd_Eye, 2> VR_EYES { { vr::Eye_Left, vr::Eye_Right } };
bool _openVrDisplayActive { false };
bool OpenVrDisplayPlugin::isSupported() const {
return openVrSupported();
@ -48,6 +53,7 @@ void OpenVrDisplayPlugin::init() {
}
bool OpenVrDisplayPlugin::internalActivate() {
_openVrDisplayActive = true;
_container->setIsOptionChecked(StandingHMDSensorMode, true);
if (!_system) {
@ -63,16 +69,14 @@ bool OpenVrDisplayPlugin::internalActivate() {
// left + right eyes
_renderTargetSize.x *= 2;
{
Lock lock(_poseMutex);
withRenderThreadLock([&] {
openvr_for_each_eye([&](vr::Hmd_Eye eye) {
_eyeOffsets[eye] = toGlm(_system->GetEyeToHeadTransform(eye));
_eyeProjections[eye] = toGlm(_system->GetProjectionMatrix(eye, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP, vr::API_OpenGL));
});
// FIXME Calculate the proper combined projection by using GetProjectionRaw values from both eyes
_cullingProjection = _eyeProjections[0];
}
});
_compositor = vr::VRCompositor();
Q_ASSERT(_compositor);
@ -100,6 +104,7 @@ bool OpenVrDisplayPlugin::internalActivate() {
void OpenVrDisplayPlugin::internalDeactivate() {
Parent::internalDeactivate();
_openVrDisplayActive = false;
_container->setIsOptionChecked(StandingHMDSensorMode, false);
if (_system) {
// Invalidate poses. It's fine if someone else sets these shared values, but we're about to stop updating them, and
@ -116,7 +121,7 @@ void OpenVrDisplayPlugin::internalDeactivate() {
void OpenVrDisplayPlugin::customizeContext() {
// Display plugins in DLLs must initialize glew locally
static std::once_flag once;
std::call_once(once, []{
std::call_once(once, [] {
glewExperimental = true;
GLenum err = glewInit();
glGetError(); // clear the potential error from glewExperimental
@ -126,9 +131,10 @@ void OpenVrDisplayPlugin::customizeContext() {
}
void OpenVrDisplayPlugin::resetSensors() {
Lock lock(_poseMutex);
glm::mat4 m = toGlm(_trackedDevicePose[0].mDeviceToAbsoluteTracking);
_sensorResetMat = glm::inverse(cancelOutRollAndPitch(m));
withRenderThreadLock([&] {
glm::mat4 m = toGlm(_trackedDevicePose[0].mDeviceToAbsoluteTracking);
_sensorResetMat = glm::inverse(cancelOutRollAndPitch(m));
});
}
@ -150,6 +156,24 @@ bool OpenVrDisplayPlugin::beginFrameRender(uint32_t frameIndex) {
_system->GetDeviceToAbsoluteTrackingPose(vr::TrackingUniverseStanding, _currentRenderFrameInfo.predictedDisplayTime, _trackedDevicePose, vr::k_unMaxTrackedDeviceCount);
vr::TrackedDeviceIndex_t handIndices[2] { vr::k_unTrackedDeviceIndexInvalid, vr::k_unTrackedDeviceIndexInvalid };
{
vr::TrackedDeviceIndex_t controllerIndices[2] ;
auto trackedCount = _system->GetSortedTrackedDeviceIndicesOfClass(vr::TrackedDeviceClass_Controller, controllerIndices, 2);
// Find the left and right hand controllers, if they exist
for (uint32_t i = 0; i < std::min<uint32_t>(trackedCount, 2); ++i) {
if (_trackedDevicePose[i].bPoseIsValid) {
auto role = _system->GetControllerRoleForTrackedDeviceIndex(controllerIndices[i]);
if (vr::TrackedControllerRole_LeftHand == role) {
handIndices[0] = controllerIndices[i];
} else if (vr::TrackedControllerRole_RightHand == role) {
handIndices[1] = controllerIndices[i];
}
}
}
}
// copy and process predictedTrackedDevicePoses
for (int i = 0; i < vr::k_unMaxTrackedDeviceCount; i++) {
_trackedDevicePoseMat4[i] = _sensorResetMat * toGlm(_trackedDevicePose[i].mDeviceToAbsoluteTracking);
@ -159,18 +183,39 @@ bool OpenVrDisplayPlugin::beginFrameRender(uint32_t frameIndex) {
_currentRenderFrameInfo.rawRenderPose = toGlm(_trackedDevicePose[vr::k_unTrackedDeviceIndex_Hmd].mDeviceToAbsoluteTracking);
_currentRenderFrameInfo.renderPose = _trackedDevicePoseMat4[vr::k_unTrackedDeviceIndex_Hmd];
Lock lock(_mutex);
_frameInfos[frameIndex] = _currentRenderFrameInfo;
return true;
bool keyboardVisible = isOpenVrKeyboardShown();
std::array<mat4, 2> handPoses;
if (!keyboardVisible) {
for (int i = 0; i < 2; ++i) {
if (handIndices[i] == vr::k_unTrackedDeviceIndexInvalid) {
continue;
}
auto deviceIndex = handIndices[i];
const mat4& mat = _trackedDevicePoseMat4[deviceIndex];
const vec3& linearVelocity = _trackedDeviceLinearVelocities[deviceIndex];
const vec3& angularVelocity = _trackedDeviceAngularVelocities[deviceIndex];
auto correctedPose = openVrControllerPoseToHandPose(i == 0, mat, linearVelocity, angularVelocity);
static const glm::quat HAND_TO_LASER_ROTATION = glm::rotation(Vectors::UNIT_Z, Vectors::UNIT_NEG_Y);
handPoses[i] = glm::translate(glm::mat4(), correctedPose.translation) * glm::mat4_cast(correctedPose.rotation * HAND_TO_LASER_ROTATION);
}
}
withRenderThreadLock([&] {
_uiModelTransform = DependencyManager::get<CompositorHelper>()->getModelTransform();
// Make controller poses available to the presentation thread
_handPoses = handPoses;
_frameInfos[frameIndex] = _currentRenderFrameInfo;
});
return Parent::beginFrameRender(frameIndex);
}
void OpenVrDisplayPlugin::hmdPresent() {
PROFILE_RANGE_EX(__FUNCTION__, 0xff00ff00, (uint64_t)_currentPresentFrameIndex)
// Flip y-axis since GL UV coords are backwards.
static vr::VRTextureBounds_t leftBounds{ 0, 0, 0.5f, 1 };
static vr::VRTextureBounds_t rightBounds{ 0.5f, 0, 1, 1 };
static vr::VRTextureBounds_t leftBounds { 0, 0, 0.5f, 1 };
static vr::VRTextureBounds_t rightBounds { 0.5f, 0, 1, 1 };
vr::Texture_t texture { (void*)oglplus::GetName(_compositeFramebuffer->color), vr::API_OpenGL, vr::ColorSpace_Auto };
@ -191,6 +236,10 @@ bool OpenVrDisplayPlugin::isHmdMounted() const {
}
void OpenVrDisplayPlugin::updatePresentPose() {
mat4 sensorResetMat;
withPresentThreadLock([&] {
sensorResetMat = _sensorResetMat;
});
{
float fSecondsSinceLastVsync;
_system->GetTimeSinceLastVsync(&fSecondsSinceLastVsync, nullptr);
@ -202,9 +251,32 @@ void OpenVrDisplayPlugin::updatePresentPose() {
_system->GetDeviceToAbsoluteTrackingPose(vr::TrackingUniverseStanding, fPredictedSecondsFromNow, &pose, 1);
_currentPresentFrameInfo.rawPresentPose = toGlm(pose.mDeviceToAbsoluteTracking);
}
_currentPresentFrameInfo.presentPose = _sensorResetMat * _currentPresentFrameInfo.rawPresentPose;
_currentPresentFrameInfo.presentPose = sensorResetMat * _currentPresentFrameInfo.rawPresentPose;
mat3 renderRotation(_currentPresentFrameInfo.rawRenderPose);
mat3 presentRotation(_currentPresentFrameInfo.rawPresentPose);
_currentPresentFrameInfo.presentReprojection = glm::mat3(glm::inverse(renderRotation) * presentRotation);
}
bool OpenVrDisplayPlugin::suppressKeyboard() {
if (isOpenVrKeyboardShown()) {
return false;
}
if (!_keyboardSupressionCount.fetch_add(1)) {
disableOpenVrKeyboard();
}
return true;
}
void OpenVrDisplayPlugin::unsuppressKeyboard() {
if (_keyboardSupressionCount == 0) {
qWarning() << "Attempted to unsuppress a keyboard that was not suppressed";
return;
}
if (1 == _keyboardSupressionCount.fetch_sub(1)) {
enableOpenVrKeyboard(_container);
}
}
bool OpenVrDisplayPlugin::isKeyboardVisible() {
return isOpenVrKeyboardShown();
}

View file

@ -32,6 +32,10 @@ public:
bool beginFrameRender(uint32_t frameIndex) override;
void cycleDebugOutput() override { _lockCurrentTexture = !_lockCurrentTexture; }
bool suppressKeyboard() override;
void unsuppressKeyboard() override;
bool isKeyboardVisible() override;
protected:
bool internalActivate() override;
void internalDeactivate() override;
@ -41,9 +45,10 @@ protected:
bool isHmdMounted() const override;
void postPreview() override;
private:
vr::IVRSystem* _system { nullptr };
std::atomic<vr::EDeviceActivityLevel> _hmdActivityLevel { vr::k_EDeviceActivityLevel_Unknown };
std::atomic<uint32_t> _keyboardSupressionCount{ 0 };
static const QString NAME;
mutable Mutex _poseMutex;
};

View file

@ -14,8 +14,16 @@
#include <QtCore/QTimer>
#include <QtCore/QLoggingCategory>
#include <QtCore/QProcessEnvironment>
#include <QtGui/QInputMethodEvent>
#include <QtQuick/QQuickWindow>
#include <Windows.h>
#include <OffscreenUi.h>
#include <controllers/Pose.h>
#include <NumericalConstants.h>
#include <ui-plugins/PluginContainer.h>
#include <ui/Menu.h>
#include "../../interface/src/Menu.h"
Q_DECLARE_LOGGING_CATEGORY(displayplugins)
Q_LOGGING_CATEGORY(displayplugins, "hifi.plugins.display")
@ -84,6 +92,146 @@ void releaseOpenVrSystem() {
}
}
static char textArray[8192];
static QMetaObject::Connection _focusConnection, _focusTextConnection, _overlayMenuConnection;
extern bool _openVrDisplayActive;
static vr::IVROverlay* _overlay { nullptr };
static QObject* _keyboardFocusObject { nullptr };
static QString _existingText;
static Qt::InputMethodHints _currentHints;
extern vr::TrackedDevicePose_t _trackedDevicePose[vr::k_unMaxTrackedDeviceCount];
static bool _keyboardShown { false };
static bool _overlayRevealed { false };
static const uint32_t SHOW_KEYBOARD_DELAY_MS = 400;
void showOpenVrKeyboard(bool show = true) {
if (!_overlay) {
return;
}
if (show) {
// To avoid flickering the keyboard when a text element is only briefly selected,
// show the keyboard asynchrnously after a very short delay, but only after we check
// that the current focus object is still one that is text enabled
QTimer::singleShot(SHOW_KEYBOARD_DELAY_MS, [] {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
auto currentFocus = offscreenUi->getWindow()->focusObject();
QInputMethodQueryEvent query(Qt::ImEnabled | Qt::ImQueryInput | Qt::ImHints);
qApp->sendEvent(currentFocus, &query);
// Current focus isn't text enabled, bail early.
if (!query.value(Qt::ImEnabled).toBool()) {
return;
}
// We're going to show the keyboard now...
_keyboardFocusObject = currentFocus;
_currentHints = Qt::InputMethodHints(query.value(Qt::ImHints).toUInt());
vr::EGamepadTextInputMode inputMode = vr::k_EGamepadTextInputModeNormal;
if (_currentHints & Qt::ImhHiddenText) {
inputMode = vr::k_EGamepadTextInputModePassword;
}
vr::EGamepadTextInputLineMode lineMode = vr::k_EGamepadTextInputLineModeSingleLine;
if (_currentHints & Qt::ImhMultiLine) {
lineMode = vr::k_EGamepadTextInputLineModeMultipleLines;
}
_existingText = query.value(Qt::ImSurroundingText).toString();
auto showKeyboardResult = _overlay->ShowKeyboard(inputMode, lineMode, "Keyboard", 1024,
_existingText.toLocal8Bit().toStdString().c_str(), false, 0);
if (vr::VROverlayError_None == showKeyboardResult) {
_keyboardShown = true;
// Try to position the keyboard slightly below where the user is looking.
mat4 headPose = cancelOutRollAndPitch(toGlm(_trackedDevicePose[0].mDeviceToAbsoluteTracking));
mat4 keyboardTransform = glm::translate(headPose, vec3(0, -0.5, -1));
keyboardTransform = keyboardTransform * glm::rotate(mat4(), 3.14159f / 4.0f, vec3(-1, 0, 0));
auto keyboardTransformVr = toOpenVr(keyboardTransform);
_overlay->SetKeyboardTransformAbsolute(vr::ETrackingUniverseOrigin::TrackingUniverseStanding, &keyboardTransformVr);
}
});
} else {
_keyboardFocusObject = nullptr;
if (_keyboardShown) {
_overlay->HideKeyboard();
_keyboardShown = false;
}
}
}
void finishOpenVrKeyboardInput() {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
auto chars = _overlay->GetKeyboardText(textArray, 8192);
auto newText = QString(QByteArray(textArray, chars));
_keyboardFocusObject->setProperty("text", newText);
//// TODO modify the new text to match the possible input hints:
//// ImhDigitsOnly ImhFormattedNumbersOnly ImhUppercaseOnly ImhLowercaseOnly
//// ImhDialableCharactersOnly ImhEmailCharactersOnly ImhUrlCharactersOnly ImhLatinOnly
//QInputMethodEvent event(_existingText, QList<QInputMethodEvent::Attribute>());
//event.setCommitString(newText, 0, _existingText.size());
//qApp->sendEvent(_keyboardFocusObject, &event);
// Simulate an enter press on the top level window to trigger the action
if (0 == (_currentHints & Qt::ImhMultiLine)) {
qApp->sendEvent(offscreenUi->getWindow(), &QKeyEvent(QEvent::KeyPress, Qt::Key_Return, Qt::KeyboardModifiers(), QString("\n")));
qApp->sendEvent(offscreenUi->getWindow(), &QKeyEvent(QEvent::KeyRelease, Qt::Key_Return, Qt::KeyboardModifiers()));
}
}
static const QString DEBUG_FLAG("HIFI_DISABLE_STEAM_VR_KEYBOARD");
bool disableSteamVrKeyboard = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG);
void enableOpenVrKeyboard(PluginContainer* container) {
if (disableSteamVrKeyboard) {
return;
}
auto offscreenUi = DependencyManager::get<OffscreenUi>();
_overlay = vr::VROverlay();
auto menu = container->getPrimaryMenu();
auto action = menu->getActionForOption(MenuOption::Overlays);
// When the overlays are revealed, suppress the keyboard from appearing on text focus for a tenth of a second.
_overlayMenuConnection = QObject::connect(action, &QAction::triggered, [action] {
if (action->isChecked()) {
_overlayRevealed = true;
const int KEYBOARD_DELAY_MS = 100;
QTimer::singleShot(KEYBOARD_DELAY_MS, [&] { _overlayRevealed = false; });
}
});
_focusConnection = QObject::connect(offscreenUi->getWindow(), &QQuickWindow::focusObjectChanged, [](QObject* object) {
if (object != _keyboardFocusObject) {
showOpenVrKeyboard(false);
}
});
_focusTextConnection = QObject::connect(offscreenUi.data(), &OffscreenUi::focusTextChanged, [](bool focusText) {
if (_openVrDisplayActive) {
if (_overlayRevealed) {
// suppress at most one text focus event
_overlayRevealed = false;
return;
}
showOpenVrKeyboard(focusText);
}
});
}
void disableOpenVrKeyboard() {
if (disableSteamVrKeyboard) {
return;
}
QObject::disconnect(_overlayMenuConnection);
QObject::disconnect(_focusTextConnection);
QObject::disconnect(_focusConnection);
}
bool isOpenVrKeyboardShown() {
return _keyboardShown;
}
void handleOpenVrEvents() {
if (!activeHmd) {
return;
@ -101,6 +249,15 @@ void handleOpenVrEvents() {
QMetaObject::invokeMethod(qApp, "quit");
break;
case vr::VREvent_KeyboardDone:
finishOpenVrKeyboardInput();
// FALL THROUGH
case vr::VREvent_KeyboardClosed:
_keyboardFocusObject = nullptr;
_keyboardShown = false;
break;
default:
break;
}
@ -108,3 +265,87 @@ void handleOpenVrEvents() {
}
}
controller::Pose openVrControllerPoseToHandPose(bool isLeftHand, const mat4& mat, const vec3& linearVelocity, const vec3& angularVelocity) {
// When the sensor-to-world rotation is identity the coordinate axes look like this:
//
// user
// forward
// -z
// |
// y| user
// y o----x right
// o-----x user
// | up
// |
// z
//
// Rift
// From ABOVE the hand canonical axes looks like this:
//
// | | | | y | | | |
// | | | | | | | | |
// | | | | |
// |left | / x---- + \ |right|
// | _/ z \_ |
// | | | |
// | | | |
//
// So when the user is in Rift space facing the -zAxis with hands outstretched and palms down
// the rotation to align the Touch axes with those of the hands is:
//
// touchToHand = halfTurnAboutY * quaterTurnAboutX
// Due to how the Touch controllers fit into the palm there is an offset that is different for each hand.
// You can think of this offset as the inverse of the measured rotation when the hands are posed, such that
// the combination (measurement * offset) is identity at this orientation.
//
// Qoffset = glm::inverse(deltaRotation when hand is posed fingers forward, palm down)
//
// An approximate offset for the Touch can be obtained by inspection:
//
// Qoffset = glm::inverse(glm::angleAxis(sign * PI/2.0f, zAxis) * glm::angleAxis(PI/4.0f, xAxis))
//
// So the full equation is:
//
// Q = combinedMeasurement * touchToHand
//
// Q = (deltaQ * QOffset) * (yFlip * quarterTurnAboutX)
//
// Q = (deltaQ * inverse(deltaQForAlignedHand)) * (yFlip * quarterTurnAboutX)
static const glm::quat yFlip = glm::angleAxis(PI, Vectors::UNIT_Y);
static const glm::quat quarterX = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_X);
static const glm::quat touchToHand = yFlip * quarterX;
static const glm::quat leftQuarterZ = glm::angleAxis(-PI_OVER_TWO, Vectors::UNIT_Z);
static const glm::quat rightQuarterZ = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_Z);
static const glm::quat eighthX = glm::angleAxis(PI / 4.0f, Vectors::UNIT_X);
static const glm::quat leftRotationOffset = glm::inverse(leftQuarterZ * eighthX) * touchToHand;
static const glm::quat rightRotationOffset = glm::inverse(rightQuarterZ * eighthX) * touchToHand;
static const float CONTROLLER_LENGTH_OFFSET = 0.0762f; // three inches
static const glm::vec3 CONTROLLER_OFFSET = glm::vec3(CONTROLLER_LENGTH_OFFSET / 2.0f,
CONTROLLER_LENGTH_OFFSET / 2.0f,
CONTROLLER_LENGTH_OFFSET * 2.0f);
static const glm::vec3 leftTranslationOffset = glm::vec3(-1.0f, 1.0f, 1.0f) * CONTROLLER_OFFSET;
static const glm::vec3 rightTranslationOffset = CONTROLLER_OFFSET;
auto translationOffset = (isLeftHand ? leftTranslationOffset : rightTranslationOffset);
auto rotationOffset = (isLeftHand ? leftRotationOffset : rightRotationOffset);
glm::vec3 position = extractTranslation(mat);
glm::quat rotation = glm::normalize(glm::quat_cast(mat));
position += rotation * translationOffset;
rotation = rotation * rotationOffset;
// transform into avatar frame
auto result = controller::Pose(position, rotation);
// handle change in velocity due to translationOffset
result.velocity = linearVelocity + glm::cross(angularVelocity, position - extractTranslation(mat));
result.angularVelocity = angularVelocity;
return result;
}

View file

@ -12,12 +12,19 @@
#include <glm/gtc/type_ptr.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <controllers/Forward.h>
#include <plugins/Forward.h>
bool openVrSupported();
vr::IVRSystem* acquireOpenVrSystem();
void releaseOpenVrSystem();
void handleOpenVrEvents();
bool openVrQuitRequested();
void enableOpenVrKeyboard(PluginContainer* container);
void disableOpenVrKeyboard();
bool isOpenVrKeyboardShown();
template<typename F>
void openvr_for_each_eye(F f) {
@ -41,3 +48,15 @@ inline mat4 toGlm(const vr::HmdMatrix34_t& m) {
m.m[0][3], m.m[1][3], m.m[2][3], 1.0f);
return result;
}
inline vr::HmdMatrix34_t toOpenVr(const mat4& m) {
vr::HmdMatrix34_t result;
for (uint8_t i = 0; i < 3; ++i) {
for (uint8_t j = 0; j < 4; ++j) {
result.m[i][j] = m[j][i];
}
}
return result;
}
controller::Pose openVrControllerPoseToHandPose(bool isLeftHand, const mat4& mat, const vec3& linearVelocity, const vec3& angularVelocity);

View file

@ -11,8 +11,6 @@
#include "ViveControllerManager.h"
#include <QtCore/QProcessEnvironment>
#include <PerfStat.h>
#include <PathUtils.h>
#include <GeometryCache.h>
@ -22,6 +20,7 @@
#include <NumericalConstants.h>
#include <ui-plugins/PluginContainer.h>
#include <UserActivityLogger.h>
#include <OffscreenUi.h>
#include <controllers/UserInputMapper.h>
@ -38,10 +37,6 @@ vr::IVRSystem* acquireOpenVrSystem();
void releaseOpenVrSystem();
static const float CONTROLLER_LENGTH_OFFSET = 0.0762f; // three inches
static const glm::vec3 CONTROLLER_OFFSET = glm::vec3(CONTROLLER_LENGTH_OFFSET / 2.0f,
CONTROLLER_LENGTH_OFFSET / 2.0f,
CONTROLLER_LENGTH_OFFSET * 2.0f);
static const char* CONTROLLER_MODEL_STRING = "vr_controller_05_wireless_b";
static const QString MENU_PARENT = "Avatar";
@ -68,6 +63,8 @@ bool ViveControllerManager::activate() {
}
Q_ASSERT(_system);
enableOpenVrKeyboard(_container);
// OpenVR provides 3d mesh representations of the controllers
// Disabled controller rendering code
/*
@ -131,6 +128,8 @@ bool ViveControllerManager::activate() {
void ViveControllerManager::deactivate() {
InputPlugin::deactivate();
disableOpenVrKeyboard();
_container->removeMenuItem(MENU_NAME, RENDER_CONTROLLERS);
_container->removeMenu(MENU_PATH);
@ -236,6 +235,12 @@ void ViveControllerManager::InputDevice::update(float deltaTime, const controlle
_poseStateMap.clear();
_buttonPressedMap.clear();
// While the keyboard is open, we defer strictly to the keyboard values
if (isOpenVrKeyboardShown()) {
_axisStateMap.clear();
return;
}
PerformanceTimer perfTimer("ViveControllerManager::update");
auto leftHandDeviceIndex = _system->GetTrackedDeviceIndexForControllerRole(vr::TrackedControllerRole_LeftHand);
@ -294,20 +299,24 @@ void ViveControllerManager::InputDevice::handleHandController(float deltaTime, u
}
// pseudo buttons the depend on both of the above for-loops
partitionTouchpad(controller::LS, controller::LX, controller::LY, controller::LS_CENTER, controller::LS_OUTER);
partitionTouchpad(controller::RS, controller::RX, controller::RY, controller::RS_CENTER, controller::RS_OUTER);
partitionTouchpad(controller::LS, controller::LX, controller::LY, controller::LS_CENTER, controller::LS_X, controller::LS_Y);
partitionTouchpad(controller::RS, controller::RX, controller::RY, controller::RS_CENTER, controller::RS_X, controller::RS_Y);
}
}
}
void ViveControllerManager::InputDevice::partitionTouchpad(int sButton, int xAxis, int yAxis, int centerPseudoButton, int outerPseudoButton) {
void ViveControllerManager::InputDevice::partitionTouchpad(int sButton, int xAxis, int yAxis, int centerPseudoButton, int xPseudoButton, int yPseudoButton) {
// Populate the L/RS_CENTER/OUTER pseudo buttons, corresponding to a partition of the L/RS space based on the X/Y values.
const float CENTER_DEADBAND = 0.6f;
const float DIAGONAL_DIVIDE_IN_RADIANS = PI / 4.0f;
if (_buttonPressedMap.find(sButton) != _buttonPressedMap.end()) {
float absX = abs(_axisStateMap[xAxis]);
float absY = abs(_axisStateMap[yAxis]);
bool isCenter = (absX < CENTER_DEADBAND) && (absY < CENTER_DEADBAND); // square deadband
_buttonPressedMap.insert(isCenter ? centerPseudoButton : outerPseudoButton);
glm::vec2 cartesianQuadrantI(absX, absY);
float angle = glm::atan(cartesianQuadrantI.y / cartesianQuadrantI.x);
float radius = glm::length(cartesianQuadrantI);
bool isCenter = radius < CENTER_DEADBAND;
_buttonPressedMap.insert(isCenter ? centerPseudoButton : ((angle < DIAGONAL_DIVIDE_IN_RADIANS) ? xPseudoButton :yPseudoButton));
}
}
@ -374,86 +383,11 @@ void ViveControllerManager::InputDevice::handleButtonEvent(float deltaTime, uint
void ViveControllerManager::InputDevice::handlePoseEvent(float deltaTime, const controller::InputCalibrationData& inputCalibrationData,
const mat4& mat, const vec3& linearVelocity,
const vec3& angularVelocity, bool isLeftHand) {
// When the sensor-to-world rotation is identity the coordinate axes look like this:
//
// user
// forward
// -z
// |
// y| user
// y o----x right
// o-----x user
// | up
// |
// z
//
// Vive
//
// From ABOVE the hand canonical axes looks like this:
//
// | | | | y | | | |
// | | | | | | | | |
// | | | | |
// |left | / x---- + \ |right|
// | _/ z \_ |
// | | | |
// | | | |
//
// So when the user is standing in Vive space facing the -zAxis with hands outstretched and palms down
// the rotation to align the Vive axes with those of the hands is:
//
// QviveToHand = halfTurnAboutY * quaterTurnAboutX
// Due to how the Vive controllers fit into the palm there is an offset that is different for each hand.
// You can think of this offset as the inverse of the measured rotation when the hands are posed, such that
// the combination (measurement * offset) is identity at this orientation.
//
// Qoffset = glm::inverse(deltaRotation when hand is posed fingers forward, palm down)
//
// An approximate offset for the Vive can be obtained by inspection:
//
// Qoffset = glm::inverse(glm::angleAxis(sign * PI/4.0f, zAxis) * glm::angleAxis(PI/2.0f, xAxis))
//
// So the full equation is:
//
// Q = combinedMeasurement * viveToHand
//
// Q = (deltaQ * QOffset) * (yFlip * quarterTurnAboutX)
//
// Q = (deltaQ * inverse(deltaQForAlignedHand)) * (yFlip * quarterTurnAboutX)
static const glm::quat yFlip = glm::angleAxis(PI, Vectors::UNIT_Y);
static const glm::quat quarterX = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_X);
static const glm::quat viveToHand = yFlip * quarterX;
static const glm::quat leftQuaterZ = glm::angleAxis(-PI_OVER_TWO, Vectors::UNIT_Z);
static const glm::quat rightQuaterZ = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_Z);
static const glm::quat eighthX = glm::angleAxis(PI / 4.0f, Vectors::UNIT_X);
static const glm::quat leftRotationOffset = glm::inverse(leftQuaterZ * eighthX) * viveToHand;
static const glm::quat rightRotationOffset = glm::inverse(rightQuaterZ * eighthX) * viveToHand;
static const glm::vec3 leftTranslationOffset = glm::vec3(-1.0f, 1.0f, 1.0f) * CONTROLLER_OFFSET;
static const glm::vec3 rightTranslationOffset = CONTROLLER_OFFSET;
auto translationOffset = (isLeftHand ? leftTranslationOffset : rightTranslationOffset);
auto rotationOffset = (isLeftHand ? leftRotationOffset : rightRotationOffset);
glm::vec3 position = extractTranslation(mat);
glm::quat rotation = glm::normalize(glm::quat_cast(mat));
position += rotation * translationOffset;
rotation = rotation * rotationOffset;
auto pose = openVrControllerPoseToHandPose(isLeftHand, mat, linearVelocity, angularVelocity);
// transform into avatar frame
glm::mat4 controllerToAvatar = glm::inverse(inputCalibrationData.avatarMat) * inputCalibrationData.sensorToWorldMat;
auto avatarPose = controller::Pose(position, rotation);
// handle change in velocity due to translationOffset
avatarPose.velocity = linearVelocity + glm::cross(angularVelocity, position - extractTranslation(mat));
avatarPose.angularVelocity = angularVelocity;
_poseStateMap[isLeftHand ? controller::LEFT_HAND : controller::RIGHT_HAND] = avatarPose.transform(controllerToAvatar);
_poseStateMap[isLeftHand ? controller::LEFT_HAND : controller::RIGHT_HAND] = pose.transform(controllerToAvatar);
}
bool ViveControllerManager::InputDevice::triggerHapticPulse(float strength, float duration, controller::Hand hand) {
@ -523,9 +457,11 @@ controller::Input::NamedVector ViveControllerManager::InputDevice::getAvailableI
makePair(RS, "RS"),
// Differentiate where we are in the touch pad click
makePair(LS_CENTER, "LSCenter"),
makePair(LS_OUTER, "LSOuter"),
makePair(LS_X, "LSX"),
makePair(LS_Y, "LSY"),
makePair(RS_CENTER, "RSCenter"),
makePair(RS_OUTER, "RSOuter"),
makePair(RS_X, "RSX"),
makePair(RS_Y, "RSY"),
// triggers
makePair(LT, "LT"),

View file

@ -64,7 +64,7 @@ private:
void handleAxisEvent(float deltaTime, uint32_t axis, float x, float y, bool isLeftHand);
void handlePoseEvent(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, const mat4& mat,
const vec3& linearVelocity, const vec3& angularVelocity, bool isLeftHand);
void ViveControllerManager::InputDevice::partitionTouchpad(int sButton, int xAxis, int yAxis, int centerPsuedoButton, int outerPseudoButton);
void partitionTouchpad(int sButton, int xAxis, int yAxis, int centerPsuedoButton, int xPseudoButton, int yPseudoButton);
class FilteredStick {
public:

View file

@ -12,6 +12,9 @@
Script.load("system/progress.js");
Script.load("system/away.js");
Script.load("system/users.js");
Script.load("system/mute.js");
Script.load("system/goto.js");
Script.load("system/hmd.js");
Script.load("system/examples.js");
Script.load("system/edit.js");
Script.load("system/selectAudioDevice.js");

View file

@ -0,0 +1,118 @@
var isActive = false;
var toolBar = (function() {
var that = {},
toolBar,
activeButton,
newModelButton,
newCubeButton,
newSphereButton,
newLightButton,
newTextButton,
newWebButton,
newZoneButton,
newParticleButton
var toolIconUrl = Script.resolvePath("../../system/assets/images/tools/");
function initialize() {
print("Toolbars: " + Toolbars);
toolBar = Toolbars.getToolbar("highfidelity.edit.toolbar");
print("Toolbar: " + toolBar);
activeButton = toolBar.addButton({
objectName: "activeButton",
imageURL: toolIconUrl + "edit-01.svg",
visible: true,
alpha: 0.9,
});
print("Button " + activeButton);
print("Button signal " + activeButton.clicked);
activeButton.clicked.connect(function(){
print("Clicked on button " + isActive);
that.setActive(!isActive);
});
newModelButton = toolBar.addButton({
objectName: "newModelButton",
imageURL: toolIconUrl + "model-01.svg",
alpha: 0.9,
visible: false
});
newCubeButton = toolBar.addButton({
objectName: "newCubeButton",
imageURL: toolIconUrl + "cube-01.svg",
alpha: 0.9,
visible: false
});
newSphereButton = toolBar.addButton({
objectName: "newSphereButton",
imageURL: toolIconUrl + "sphere-01.svg",
alpha: 0.9,
visible: false
});
newLightButton = toolBar.addButton({
objectName: "newLightButton",
imageURL: toolIconUrl + "light-01.svg",
alpha: 0.9,
visible: false
});
newTextButton = toolBar.addButton({
objectName: "newTextButton",
imageURL: toolIconUrl + "text-01.svg",
alpha: 0.9,
visible: false
});
newWebButton = toolBar.addButton({
objectName: "newWebButton",
imageURL: toolIconUrl + "web-01.svg",
alpha: 0.9,
visible: false
});
newZoneButton = toolBar.addButton({
objectName: "newZoneButton",
imageURL: toolIconUrl + "zone-01.svg",
alpha: 0.9,
visible: false
});
newParticleButton = toolBar.addButton({
objectName: "newParticleButton",
imageURL: toolIconUrl + "particle-01.svg",
alpha: 0.9,
visible: false
});
that.setActive(false);
newModelButton.clicked();
}
that.setActive = function(active) {
if (active != isActive) {
isActive = active;
that.showTools(isActive);
}
};
// Sets visibility of tool buttons, excluding the power button
that.showTools = function(doShow) {
newModelButton.writeProperty('visible', doShow);
newCubeButton.writeProperty('visible', doShow);
newSphereButton.writeProperty('visible', doShow);
newLightButton.writeProperty('visible', doShow);
newTextButton.writeProperty('visible', doShow);
newWebButton.writeProperty('visible', doShow);
newZoneButton.writeProperty('visible', doShow);
newModelButton.writeProperty('visible', doShow);
newParticleButton.writeProperty('visible', doShow);
};
initialize();
return that;
}());

View file

@ -0,0 +1,124 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 50 150" style="enable-background:new 0 0 50 150;" xml:space="preserve">
<style type="text/css">
.st0{opacity:0.49;}
.st1{opacity:0.9;}
.st2{fill:#1E1E1E;}
.st3{fill:#FFFFFF;}
.st4{fill:#EAEAEA;}
.st5{opacity:0.49;fill:#EAEAEA;}
</style>
<g class="st0">
<g class="st1">
<path class="st2" d="M50,146.4c0,2.2-1.8,4-4,4H4c-2.2,0-4-1.8-4-4v-42c0-2.2,1.8-4,4-4h42c2.2,0,4,1.8,4,4V146.4z"/>
</g>
</g>
<g class="st1">
<path class="st3" d="M50,46.6c0,2.2-1.8,4-4,4H4c-2.2,0-4-1.8-4-4v-42c0-2.2,1.8-4,4-4h42c2.2,0,4,1.8,4,4V46.6z"/>
</g>
<path d="M27.3,21.1v3.5c0,0.6-0.5,1.1-1.1,1.1H13c-0.6,0-1.1-0.5-1.1-1.1v-6.5c0-0.6,0.5-1.1,1.1-1.1h3.8c-0.1-0.4-0.3-0.9-0.3-1.3
H13c-1.4,0-2.4,1.1-2.4,2.4v6.5c0,1.4,1.1,2.4,2.4,2.4h5.9v1.2h-3.4c-0.3,0-0.6,0.3-0.6,0.7c0,0.3,0.3,0.7,0.6,0.7h8.5
c0.3,0,0.6-0.3,0.6-0.7c0-0.3-0.3-0.7-0.6-0.7h-3.7V27c-0.1,0-0.2,0-0.4,0h6.3c1.4,0,2.4-1.1,2.4-2.4v-4.4
C28.2,20.5,27.8,20.8,27.3,21.1z"/>
<path d="M35.7,19.8h-3.7c-0.9,0-1.6-0.9-2.1-1.7c-0.2-0.3-0.6-0.8-0.8-1c-0.2,0.1-0.6,0.6-0.8,0.9c-0.6,0.8-1.2,1.8-2.2,1.8h-3.5
c-2.8,0-5-2.2-5-5v-2.3c0-2.8,2.3-5,5-5h13.1c2.8,0,5,2.2,5,5v2.3C40.8,17.6,38.5,19.8,35.7,19.8z M29.2,15.8c0.8,0,1.4,0.8,1.9,1.6
c0.2,0.4,0.8,1.1,1,1.1h3.7c2,0,3.7-1.6,3.7-3.7v-2.3c0-2-1.7-3.7-3.7-3.7H22.7c-2,0-3.7,1.6-3.7,3.7v2.3c0,2,1.7,3.7,3.7,3.7h3.5
c0.3,0,0.8-0.7,1.1-1.2C27.8,16.5,28.4,15.8,29.2,15.8C29.2,15.8,29.2,15.8,29.2,15.8z"/>
<path d="M36.9,21.6c-0.2-0.1-0.4,0-0.6,0.1l-1.8,1.8l0.7,0.7l0.9-0.9c0,0,0.3,1.3-0.7,2.4c-1,1-2.3,0.6-2.3,0.6l0.8-0.8l-0.8-0.8
l-1.7,1.7l0,0c0,0-0.1,0.1-0.1,0.2c-0.1,0.2,0,0.4,0.1,0.6l1.8,1.8l0.8-0.8l-0.9-0.9c0,0,1.7,0.3,3.1-1.1c1.3-1.3,1-3.1,1-3.1
l0.8,0.8l0.8-0.8l-1.6-1.6C37.2,21.8,37,21.7,36.9,21.6z"/>
<g>
<path d="M10.3,37.9c0,0-0.1-0.1-0.2-0.2c-0.1-0.1-0.2-0.1-0.4-0.2c-0.2-0.1-0.3-0.1-0.5-0.2c-0.2,0-0.4-0.1-0.6-0.1
c-0.3,0-0.6,0.1-0.8,0.2c-0.2,0.1-0.3,0.3-0.3,0.5c0,0.1,0,0.2,0.1,0.3c0.1,0.1,0.2,0.2,0.3,0.2s0.3,0.1,0.5,0.2
c0.2,0.1,0.4,0.1,0.6,0.2c0.3,0.1,0.6,0.2,0.9,0.3c0.3,0.1,0.5,0.2,0.6,0.4c0.2,0.1,0.3,0.3,0.4,0.5c0.1,0.2,0.1,0.4,0.1,0.7
c0,0.3-0.1,0.6-0.2,0.9c-0.1,0.2-0.3,0.4-0.5,0.6s-0.5,0.3-0.8,0.3c-0.3,0.1-0.6,0.1-0.9,0.1c-0.5,0-1-0.1-1.4-0.2S6.4,42.2,6,41.9
l0.5-1.1c0.1,0.1,0.2,0.1,0.3,0.2c0.1,0.1,0.3,0.2,0.5,0.3s0.4,0.2,0.6,0.2c0.2,0.1,0.5,0.1,0.7,0.1c0.7,0,1-0.2,1-0.7
c0-0.1,0-0.3-0.1-0.4c-0.1-0.1-0.2-0.2-0.3-0.3c-0.1-0.1-0.3-0.1-0.5-0.2c-0.2-0.1-0.4-0.1-0.7-0.2c-0.3-0.1-0.6-0.2-0.8-0.3
s-0.4-0.2-0.6-0.3c-0.2-0.1-0.3-0.3-0.3-0.5c-0.1-0.2-0.1-0.4-0.1-0.6c0-0.3,0.1-0.6,0.2-0.9c0.1-0.3,0.3-0.5,0.5-0.6
s0.5-0.3,0.7-0.4c0.3-0.1,0.6-0.1,0.9-0.1c0.5,0,0.9,0.1,1.2,0.2c0.4,0.1,0.7,0.3,1,0.5L10.3,37.9z"/>
<path d="M14.3,36.3h1.1l0.7,2.1l0.7-2.1H18L17,39.2l0.8,2l1.8-5h1.4l-2.6,6.4h-1L16.2,40l-1.1,2.7h-1l-2.6-6.4h1.3l1.8,5l0.8-2
L14.3,36.3z"/>
<path d="M21.8,42.6v-6.4h1.2v6.4H21.8z"/>
<path d="M29.4,37.3h-2v5.3h-1.2v-5.3h-2v-1.1h5.3V37.3z"/>
<path d="M30,39.4c0-0.4,0.1-0.8,0.2-1.2c0.1-0.4,0.3-0.7,0.6-1c0.3-0.3,0.6-0.5,1-0.7s0.8-0.3,1.3-0.3c0.6,0,1.1,0.1,1.5,0.4
c0.4,0.3,0.7,0.6,0.9,1l-1,0.7c-0.1-0.2-0.2-0.3-0.3-0.5c-0.1-0.1-0.2-0.2-0.4-0.3c-0.1-0.1-0.3-0.1-0.4-0.2c-0.1,0-0.3,0-0.4,0
c-0.3,0-0.6,0.1-0.8,0.2s-0.4,0.3-0.6,0.5c-0.1,0.2-0.3,0.4-0.3,0.7s-0.1,0.5-0.1,0.8c0,0.3,0,0.6,0.1,0.8c0.1,0.3,0.2,0.5,0.4,0.7
c0.2,0.2,0.3,0.4,0.6,0.5c0.2,0.1,0.5,0.2,0.7,0.2c0.1,0,0.3,0,0.4-0.1c0.1,0,0.3-0.1,0.4-0.2c0.1-0.1,0.3-0.2,0.4-0.3
c0.1-0.1,0.2-0.3,0.3-0.4l1,0.6c-0.1,0.2-0.2,0.5-0.4,0.6c-0.2,0.2-0.4,0.3-0.6,0.5c-0.2,0.1-0.5,0.2-0.7,0.3
c-0.3,0.1-0.5,0.1-0.8,0.1c-0.4,0-0.9-0.1-1.2-0.3c-0.4-0.2-0.7-0.4-1-0.8c-0.3-0.3-0.5-0.7-0.6-1.1C30.1,40.2,30,39.8,30,39.4z"/>
<path d="M42,36.3v6.4h-1.2v-2.7h-2.9v2.7h-1.2v-6.4h1.2v2.6h2.9v-2.6H42z"/>
</g>
<g class="st1">
<path class="st2" d="M50,96.6c0,2.2-1.8,4-4,4H4c-2.2,0-4-1.8-4-4v-42c0-2.2,1.8-4,4-4h42c2.2,0,4,1.8,4,4V96.6z"/>
</g>
<path class="st4" d="M27.3,71.1v3.5c0,0.6-0.5,1.1-1.1,1.1H13c-0.6,0-1.1-0.5-1.1-1.1v-6.5c0-0.6,0.5-1.1,1.1-1.1h3.8
c-0.1-0.4-0.3-0.9-0.3-1.3H13c-1.4,0-2.4,1.1-2.4,2.4v6.5c0,1.4,1.1,2.4,2.4,2.4h5.9v1.2h-3.4c-0.3,0-0.6,0.3-0.6,0.7
c0,0.3,0.3,0.7,0.6,0.7h8.5c0.3,0,0.6-0.3,0.6-0.7c0-0.3-0.3-0.7-0.6-0.7h-3.7V77c-0.1,0-0.2,0-0.4,0h6.3c1.4,0,2.4-1.1,2.4-2.4
v-4.4C28.2,70.5,27.8,70.8,27.3,71.1z"/>
<path class="st4" d="M35.7,69.8h-3.7c-0.9,0-1.6-0.9-2.1-1.7c-0.2-0.3-0.6-0.8-0.8-1c-0.2,0.1-0.6,0.6-0.8,0.9
c-0.6,0.8-1.2,1.8-2.2,1.8h-3.5c-2.8,0-5-2.2-5-5v-2.3c0-2.8,2.3-5,5-5h13.1c2.8,0,5,2.2,5,5v2.3C40.8,67.6,38.5,69.8,35.7,69.8z
M29.2,65.8c0.8,0,1.4,0.8,1.9,1.6c0.2,0.4,0.8,1.1,1,1.1h3.7c2,0,3.7-1.6,3.7-3.7v-2.3c0-2-1.7-3.7-3.7-3.7H22.7
c-2,0-3.7,1.6-3.7,3.7v2.3c0,2,1.7,3.7,3.7,3.7h3.5c0.3,0,0.8-0.7,1.1-1.2C27.8,66.5,28.4,65.8,29.2,65.8
C29.2,65.8,29.2,65.8,29.2,65.8z"/>
<path class="st4" d="M36.9,71.6c-0.2-0.1-0.4,0-0.6,0.1l-1.8,1.8l0.7,0.7l0.9-0.9c0,0,0.3,1.3-0.7,2.4c-1,1-2.3,0.6-2.3,0.6l0.8-0.8
l-0.8-0.8l-1.7,1.7l0,0c0,0-0.1,0.1-0.1,0.2c-0.1,0.2,0,0.4,0.1,0.6l1.8,1.8l0.8-0.8l-0.9-0.9c0,0,1.7,0.3,3.1-1.1
c1.3-1.3,1-3.1,1-3.1l0.8,0.8l0.8-0.8l-1.6-1.6C37.2,71.8,37,71.7,36.9,71.6z"/>
<g>
<path class="st4" d="M10.3,87.9c0,0-0.1-0.1-0.2-0.2c-0.1-0.1-0.2-0.1-0.4-0.2s-0.3-0.1-0.5-0.2c-0.2,0-0.4-0.1-0.6-0.1
c-0.3,0-0.6,0.1-0.8,0.2S7.6,87.8,7.6,88c0,0.1,0,0.2,0.1,0.3c0.1,0.1,0.2,0.2,0.3,0.2c0.1,0.1,0.3,0.1,0.5,0.2
c0.2,0.1,0.4,0.1,0.6,0.2c0.3,0.1,0.6,0.2,0.9,0.3s0.5,0.2,0.6,0.4c0.2,0.1,0.3,0.3,0.4,0.5c0.1,0.2,0.1,0.4,0.1,0.7
c0,0.3-0.1,0.6-0.2,0.9c-0.1,0.2-0.3,0.4-0.5,0.6c-0.2,0.2-0.5,0.3-0.8,0.3c-0.3,0.1-0.6,0.1-0.9,0.1c-0.5,0-1-0.1-1.4-0.2
c-0.5-0.1-0.9-0.3-1.3-0.6l0.5-1.1c0.1,0.1,0.2,0.1,0.3,0.2c0.1,0.1,0.3,0.2,0.5,0.3s0.4,0.2,0.6,0.2c0.2,0.1,0.5,0.1,0.7,0.1
c0.7,0,1-0.2,1-0.7c0-0.1,0-0.3-0.1-0.4c-0.1-0.1-0.2-0.2-0.3-0.3c-0.1-0.1-0.3-0.1-0.5-0.2c-0.2-0.1-0.4-0.1-0.7-0.2
c-0.3-0.1-0.6-0.2-0.8-0.3c-0.2-0.1-0.4-0.2-0.6-0.3S6.5,89,6.4,88.8c-0.1-0.2-0.1-0.4-0.1-0.6c0-0.3,0.1-0.6,0.2-0.9
c0.1-0.3,0.3-0.5,0.5-0.6s0.5-0.3,0.7-0.4s0.6-0.1,0.9-0.1c0.5,0,0.9,0.1,1.2,0.2s0.7,0.3,1,0.5L10.3,87.9z"/>
<path class="st4" d="M14.3,86.3h1.1l0.7,2.1l0.7-2.1H18L17,89.2l0.8,2l1.8-5h1.4l-2.6,6.4h-1L16.2,90l-1.1,2.7h-1l-2.6-6.4h1.3
l1.8,5l0.8-2L14.3,86.3z"/>
<path class="st4" d="M21.8,92.6v-6.4h1.2v6.4H21.8z"/>
<path class="st4" d="M29.4,87.3h-2v5.3h-1.2v-5.3h-2v-1.1h5.3V87.3z"/>
<path class="st4" d="M30,89.4c0-0.4,0.1-0.8,0.2-1.2c0.1-0.4,0.3-0.7,0.6-1c0.3-0.3,0.6-0.5,1-0.7c0.4-0.2,0.8-0.3,1.3-0.3
c0.6,0,1.1,0.1,1.5,0.4s0.7,0.6,0.9,1l-1,0.7c-0.1-0.2-0.2-0.3-0.3-0.5c-0.1-0.1-0.2-0.2-0.4-0.3s-0.3-0.1-0.4-0.2
c-0.1,0-0.3,0-0.4,0c-0.3,0-0.6,0.1-0.8,0.2s-0.4,0.3-0.6,0.5c-0.1,0.2-0.3,0.4-0.3,0.7s-0.1,0.5-0.1,0.8c0,0.3,0,0.6,0.1,0.8
c0.1,0.3,0.2,0.5,0.4,0.7c0.2,0.2,0.3,0.4,0.6,0.5c0.2,0.1,0.5,0.2,0.7,0.2c0.1,0,0.3,0,0.4-0.1c0.1,0,0.3-0.1,0.4-0.2
c0.1-0.1,0.3-0.2,0.4-0.3c0.1-0.1,0.2-0.3,0.3-0.4l1,0.6c-0.1,0.2-0.2,0.5-0.4,0.6c-0.2,0.2-0.4,0.3-0.6,0.5
c-0.2,0.1-0.5,0.2-0.7,0.3c-0.3,0.1-0.5,0.1-0.8,0.1c-0.4,0-0.9-0.1-1.2-0.3c-0.4-0.2-0.7-0.4-1-0.8c-0.3-0.3-0.5-0.7-0.6-1.1
C30.1,90.2,30,89.8,30,89.4z"/>
<path class="st4" d="M42,86.2v6.4h-1.2v-2.7h-2.9v2.7h-1.2v-6.4h1.2v2.6h2.9v-2.6H42z"/>
</g>
<path class="st5" d="M27.3,121.1v3.5c0,0.6-0.5,1.1-1.1,1.1H13c-0.6,0-1.1-0.5-1.1-1.1v-6.5c0-0.6,0.5-1.1,1.1-1.1h3.8
c-0.1-0.4-0.3-0.9-0.3-1.3H13c-1.4,0-2.4,1.1-2.4,2.4v6.5c0,1.4,1.1,2.4,2.4,2.4h5.9v1.2h-3.4c-0.3,0-0.6,0.3-0.6,0.7
c0,0.3,0.3,0.7,0.6,0.7h8.5c0.3,0,0.6-0.3,0.6-0.7c0-0.3-0.3-0.7-0.6-0.7h-3.7V127c-0.1,0-0.2,0-0.4,0h6.3c1.4,0,2.4-1.1,2.4-2.4
v-4.4C28.2,120.5,27.8,120.8,27.3,121.1z"/>
<path class="st5" d="M35.7,119.8h-3.7c-0.9,0-1.6-0.9-2.1-1.7c-0.2-0.3-0.6-0.8-0.8-1c-0.2,0.1-0.6,0.6-0.8,0.9
c-0.6,0.8-1.2,1.8-2.2,1.8h-3.5c-2.8,0-5-2.2-5-5v-2.3c0-2.8,2.3-5,5-5h13.1c2.8,0,5,2.2,5,5v2.3C40.8,117.6,38.5,119.8,35.7,119.8z
M29.2,115.8c0.8,0,1.4,0.8,1.9,1.6c0.2,0.4,0.8,1.1,1,1.1h3.7c2,0,3.7-1.6,3.7-3.7v-2.3c0-2-1.7-3.7-3.7-3.7H22.7
c-2,0-3.7,1.6-3.7,3.7v2.3c0,2,1.7,3.7,3.7,3.7h3.5c0.3,0,0.8-0.7,1.1-1.2C27.8,116.5,28.4,115.8,29.2,115.8
C29.2,115.8,29.2,115.8,29.2,115.8z"/>
<path class="st5" d="M36.9,121.6c-0.2-0.1-0.4,0-0.6,0.1l-1.8,1.8l0.7,0.7l0.9-0.9c0,0,0.3,1.3-0.7,2.4c-1,1-2.3,0.6-2.3,0.6
l0.8-0.8l-0.8-0.8l-1.7,1.7l0,0c0,0-0.1,0.1-0.1,0.2c-0.1,0.2,0,0.4,0.1,0.6l1.8,1.8l0.8-0.8l-0.9-0.9c0,0,1.7,0.3,3.1-1.1
c1.3-1.3,1-3.1,1-3.1l0.8,0.8l0.8-0.8l-1.6-1.6C37.2,121.8,37,121.7,36.9,121.6z"/>
<g class="st0">
<path class="st4" d="M10.3,137.9c0,0-0.1-0.1-0.2-0.2c-0.1-0.1-0.2-0.1-0.4-0.2s-0.3-0.1-0.5-0.2c-0.2,0-0.4-0.1-0.6-0.1
c-0.3,0-0.6,0.1-0.8,0.2s-0.3,0.3-0.3,0.5c0,0.1,0,0.2,0.1,0.3c0.1,0.1,0.2,0.2,0.3,0.2c0.1,0.1,0.3,0.1,0.5,0.2
c0.2,0.1,0.4,0.1,0.6,0.2c0.3,0.1,0.6,0.2,0.9,0.3s0.5,0.2,0.6,0.4c0.2,0.1,0.3,0.3,0.4,0.5c0.1,0.2,0.1,0.4,0.1,0.7
c0,0.3-0.1,0.6-0.2,0.9c-0.1,0.2-0.3,0.4-0.5,0.6c-0.2,0.2-0.5,0.3-0.8,0.3c-0.3,0.1-0.6,0.1-0.9,0.1c-0.5,0-1-0.1-1.4-0.2
c-0.5-0.1-0.9-0.3-1.3-0.6l0.5-1.1c0.1,0.1,0.2,0.1,0.3,0.2c0.1,0.1,0.3,0.2,0.5,0.3s0.4,0.2,0.6,0.2c0.2,0.1,0.5,0.1,0.7,0.1
c0.7,0,1-0.2,1-0.7c0-0.1,0-0.3-0.1-0.4c-0.1-0.1-0.2-0.2-0.3-0.3c-0.1-0.1-0.3-0.1-0.5-0.2c-0.2-0.1-0.4-0.1-0.7-0.2
c-0.3-0.1-0.6-0.2-0.8-0.3c-0.2-0.1-0.4-0.2-0.6-0.3s-0.3-0.3-0.3-0.5c-0.1-0.2-0.1-0.4-0.1-0.6c0-0.3,0.1-0.6,0.2-0.9
c0.1-0.3,0.3-0.5,0.5-0.6s0.5-0.3,0.7-0.4s0.6-0.1,0.9-0.1c0.5,0,0.9,0.1,1.2,0.2s0.7,0.3,1,0.5L10.3,137.9z"/>
<path class="st4" d="M14.3,136.3h1.1l0.7,2.1l0.7-2.1H18l-1.1,2.9l0.8,2l1.8-5h1.4l-2.6,6.4h-1l-1.1-2.7l-1.1,2.7h-1l-2.6-6.4h1.3
l1.8,5l0.8-2L14.3,136.3z"/>
<path class="st4" d="M21.8,142.6v-6.4h1.2v6.4H21.8z"/>
<path class="st4" d="M29.4,137.3h-2v5.3h-1.2v-5.3h-2v-1.1h5.3V137.3z"/>
<path class="st4" d="M30,139.4c0-0.4,0.1-0.8,0.2-1.2c0.1-0.4,0.3-0.7,0.6-1c0.3-0.3,0.6-0.5,1-0.7c0.4-0.2,0.8-0.3,1.3-0.3
c0.6,0,1.1,0.1,1.5,0.4s0.7,0.6,0.9,1l-1,0.7c-0.1-0.2-0.2-0.3-0.3-0.5c-0.1-0.1-0.2-0.2-0.4-0.3s-0.3-0.1-0.4-0.2
c-0.1,0-0.3,0-0.4,0c-0.3,0-0.6,0.1-0.8,0.2s-0.4,0.3-0.6,0.5c-0.1,0.2-0.3,0.4-0.3,0.7s-0.1,0.5-0.1,0.8c0,0.3,0,0.6,0.1,0.8
c0.1,0.3,0.2,0.5,0.4,0.7c0.2,0.2,0.3,0.4,0.6,0.5c0.2,0.1,0.5,0.2,0.7,0.2c0.1,0,0.3,0,0.4-0.1c0.1,0,0.3-0.1,0.4-0.2
c0.1-0.1,0.3-0.2,0.4-0.3c0.1-0.1,0.2-0.3,0.3-0.4l1,0.6c-0.1,0.2-0.2,0.5-0.4,0.6c-0.2,0.2-0.4,0.3-0.6,0.5
c-0.2,0.1-0.5,0.2-0.7,0.3c-0.3,0.1-0.5,0.1-0.8,0.1c-0.4,0-0.9-0.1-1.2-0.3c-0.4-0.2-0.7-0.4-1-0.8c-0.3-0.3-0.5-0.7-0.6-1.1
C30.1,140.2,30,139.8,30,139.4z"/>
<path class="st4" d="M42,136.2v6.4h-1.2v-2.7h-2.9v2.7h-1.2v-6.4h1.2v2.6h2.9v-2.6H42z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 10 KiB

View file

@ -0,0 +1,13 @@
<?xml version="1.0"?>
<svg width="50" height="50.1" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
<metadata id="metadata27">image/svg+xml</metadata>
<g>
<title>Layer 1</title>
<g id="g3" opacity="0.9">
<path id="path5" d="m50,46c0,2.2 -1.8,4 -4,4l-42,0c-2.2,0 -4,-1.8 -4,-4l0,-42c0,-2.2 1.8,-4 4,-4l42,0c2.2,0 4,1.8 4,4l0,42z" fill="#1E1E1E"/>
</g>
<text xml:space="preserve" text-anchor="middle" font-family="Sans" font-size="11px" id="svg_5" y="42.610549" x="25.949214" stroke="#cbcbcb" fill="#cbcbcb">Mute</text>
<ellipse ry="6.644531" rx="0.632813" id="svg_6" cy="15.716017" cx="25.316401" stroke-width="5" stroke="#cbcbcb" fill="#1E1E1E"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 702 B

View file

@ -178,9 +178,11 @@ function goAway() {
// tell the Reticle, we want to stop capturing the mouse until we come back
Reticle.allowMouseCapture = false;
if (HMD.active) {
Reticle.visible = false;
}
// Allow users to find their way to other applications, our menus, etc.
// For desktop, that means we want the reticle visible.
// For HMD, the hmd preview will show the system mouse because of allowMouseCapture,
// but we want to turn off our Reticle so that we don't get two in preview and a stuck one in headset.
Reticle.visible = !HMD.active;
wasHmdMounted = safeGetHMDMounted(); // always remember the correct state
avatarPosition = MyAvatar.position;

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
"use strict";
/*jslint vars: true, plusplus: true*/
/*globals Script, Overlays, Controller, Reticle, HMD, Camera, Entities, MyAvatar, Settings, Menu, ScriptDiscoveryService, Window, Vec3, Quat, print */
/*globals Script, Overlays, Controller, Reticle, HMD, Camera, Entities, MyAvatar, Settings, Menu, ScriptDiscoveryService, Window, Vec3, Quat, print*/
//
// handControllerPointer.js
@ -14,19 +14,16 @@
//
// Control the "mouse" using hand controller. (HMD and desktop.)
// For now:
// Hydra thumb button 3 is left-mouse, button 4 is right-mouse.
// A click in the center of the vive thumb pad is left mouse. Vive menu button is context menu (right mouse).
// First-person only.
// Starts right handed, but switches to whichever is free: Whichever hand was NOT most recently squeezed.
// (For now, the thumb buttons on both controllers are always on.)
// When over a HUD element, the reticle is shown where the active hand controller beam intersects the HUD.
// Otherwise, the active hand controller shows a red ball where a click will act.
// When partially squeezing over a HUD element, a laser or the reticle is shown where the active hand
// controller beam intersects the HUD.
// UTILITIES -------------
//
function ignore() { }
// Utility to make it easier to setup and disconnect cleanly.
function setupHandler(event, handler) {
@ -47,21 +44,61 @@ function TimeLock(expiration) {
}
var handControllerLockOut = new TimeLock(2000);
// Calls onFunction() or offFunction() when swtich(on), but only if it is to a new value.
function LatchedToggle(onFunction, offFunction, state) {
this.getState = function () {
return state;
function Trigger(label) {
// This part is copied and adapted from handControllerGrab.js. Maybe we should refactor this.
var that = this;
that.label = label;
that.TRIGGER_SMOOTH_RATIO = 0.1; // Time averaging of trigger - 0.0 disables smoothing
that.TRIGGER_ON_VALUE = 0.4; // Squeezed just enough to activate search or near grab
that.TRIGGER_GRAB_VALUE = 0.85; // Squeezed far enough to complete distant grab
that.TRIGGER_OFF_VALUE = 0.15;
that.rawTriggerValue = 0;
that.triggerValue = 0; // rolling average of trigger value
that.triggerPress = function (value) {
that.rawTriggerValue = value;
};
this.setState = function (on) {
if (state === on) {
return;
}
state = on;
if (on) {
onFunction();
} else {
offFunction();
that.updateSmoothedTrigger = function () { // e.g., call once/update for effect
var triggerValue = that.rawTriggerValue;
// smooth out trigger value
that.triggerValue = (that.triggerValue * that.TRIGGER_SMOOTH_RATIO) +
(triggerValue * (1.0 - that.TRIGGER_SMOOTH_RATIO));
};
// Current smoothed state, without hysteresis. Answering booleans.
that.triggerSmoothedGrab = function () {
return that.triggerValue > that.TRIGGER_GRAB_VALUE;
};
that.triggerSmoothedSqueezed = function () {
return that.triggerValue > that.TRIGGER_ON_VALUE;
};
that.triggerSmoothedReleased = function () {
return that.triggerValue < that.TRIGGER_OFF_VALUE;
};
// This part is not from handControllerGrab.js
that.state = null; // tri-state: falsey, 'partial', 'full'
that.update = function () { // update state, called from an update function
var state = that.state;
that.updateSmoothedTrigger();
// The first two are independent of previous state:
if (that.triggerSmoothedGrab()) {
state = 'full';
} else if (that.triggerSmoothedReleased()) {
state = null;
} else if (that.triggerSmoothedSqueezed()) {
// Another way to do this would be to have hysteresis in this branch, but that seems to make things harder to use.
// In particular, the vive has a nice detent as you release off of full, and we want that to be a transition from
// full to partial.
state = 'partial';
}
that.state = state;
};
// Answer a controller source function (answering either 0.0 or 1.0).
that.partial = function () {
return that.state ? 1.0 : 0.0; // either 'partial' or 'full'
};
that.full = function () {
return (that.state === 'full') ? 1.0 : 0.0;
};
}
@ -107,9 +144,8 @@ function isPointingAtOverlay(optionalHudPosition2d) {
}
// Generalized HUD utilities, with or without HMD:
// These two "vars" are for documentation. Do not change their values!
var SPHERICAL_HUD_DISTANCE = 1; // meters.
var PLANAR_PERPENDICULAR_HUD_DISTANCE = SPHERICAL_HUD_DISTANCE;
// This "var" is for documentation. Do not change the value!
var PLANAR_PERPENDICULAR_HUD_DISTANCE = 1;
function calculateRayUICollisionPoint(position, direction) {
// Answer the 3D intersection of the HUD by the given ray, or falsey if no intersection.
if (HMD.active) {
@ -185,10 +221,9 @@ function updateSeeking() {
}
averageMouseVelocity = lastIntegration = 0;
var lookAt2D = HMD.getHUDLookAtPosition2D();
if (!lookAt2D) {
// FIXME - determine if this message is useful but make it so it doesn't spam the
// log in the case that it is happening
//print('Cannot seek without lookAt position');
if (!lookAt2D) { // If this happens, something has gone terribly wrong.
print('Cannot seek without lookAt position');
isSeeking = false;
return;
} // E.g., if parallel to location in HUD
var copy = Reticle.position;
@ -229,6 +264,11 @@ function expireMouseCursor(now) {
Reticle.visible = false;
}
}
function hudReticleDistance() { // 3d distance from camera to the reticle position on hud
// (The camera is only in the center of the sphere on reset.)
var reticlePositionOnHUD = HMD.worldPointFromOverlay(Reticle.position);
return Vec3.distance(reticlePositionOnHUD, HMD.position);
}
function onMouseMove() {
// Display cursor at correct depth (as in depthReticle.js), and updateMouseActivity.
if (ignoreMouseActivity()) {
@ -238,11 +278,10 @@ function onMouseMove() {
if (HMD.active) { // set depth
updateSeeking();
if (isPointingAtOverlay()) {
Reticle.setDepth(SPHERICAL_HUD_DISTANCE); // NOT CORRECT IF WE SWITCH TO OFFSET SPHERE!
Reticle.depth = hudReticleDistance();
} else {
var result = findRayIntersection(Camera.computePickRay(Reticle.position.x, Reticle.position.y));
var depth = result.intersects ? result.distance : APPARENT_MAXIMUM_DEPTH;
Reticle.setDepth(depth);
Reticle.depth = result.intersects ? result.distance : APPARENT_MAXIMUM_DEPTH;
}
}
updateMouseActivity(); // After the above, just in case the depth movement is awkward when becoming visible.
@ -257,113 +296,112 @@ setupHandler(Controller.mouseDoublePressEvent, onMouseClick);
// CONTROLLER MAPPING ---------
//
var leftTrigger = new Trigger('left');
var rightTrigger = new Trigger('right');
var activeTrigger = rightTrigger;
var activeHand = Controller.Standard.RightHand;
function toggleHand() {
var LEFT_HUD_LASER = 1;
var RIGHT_HUD_LASER = 2;
var BOTH_HUD_LASERS = LEFT_HUD_LASER + RIGHT_HUD_LASER;
var activeHudLaser = RIGHT_HUD_LASER;
function toggleHand() { // unequivocally switch which hand controls mouse position
if (activeHand === Controller.Standard.RightHand) {
activeHand = Controller.Standard.LeftHand;
activeTrigger = leftTrigger;
activeHudLaser = LEFT_HUD_LASER;
} else {
activeHand = Controller.Standard.RightHand;
activeTrigger = rightTrigger;
activeHudLaser = RIGHT_HUD_LASER;
}
clearSystemLaser();
}
function makeToggleAction(hand) { // return a function(0|1) that makes the specified hand control mouse when 1
return function (on) {
if (on && (activeHand !== hand)) {
toggleHand();
}
};
}
var clickMapping = Controller.newMapping(Script.resolvePath('') + '-click');
Script.scriptEnding.connect(clickMapping.disable);
clickMapping.from(Controller.Standard.RightPrimaryThumb).peek().to(Controller.Actions.ReticleClick);
clickMapping.from(Controller.Standard.LeftPrimaryThumb).peek().to(Controller.Actions.ReticleClick);
// Gather the trigger data for smoothing.
clickMapping.from(Controller.Standard.RT).peek().to(rightTrigger.triggerPress);
clickMapping.from(Controller.Standard.LT).peek().to(leftTrigger.triggerPress);
// Full smoothed trigger is a click.
function isPointingAtOverlayStartedNonFullTrigger(trigger) {
// true if isPointingAtOverlay AND we were NOT full triggered when we became so.
// The idea is to not count clicks when we're full-triggering and reach the edge of a window.
var lockedIn = false;
return function () {
if (trigger !== activeTrigger) {
return lockedIn = false;
}
if (!isPointingAtOverlay()) {
return lockedIn = false;
}
if (lockedIn) {
return true;
}
lockedIn = !trigger.full();
return lockedIn;
}
}
clickMapping.from(rightTrigger.full).when(isPointingAtOverlayStartedNonFullTrigger(rightTrigger)).to(Controller.Actions.ReticleClick);
clickMapping.from(leftTrigger.full).when(isPointingAtOverlayStartedNonFullTrigger(leftTrigger)).to(Controller.Actions.ReticleClick);
clickMapping.from(Controller.Standard.RightSecondaryThumb).peek().to(Controller.Actions.ContextMenu);
clickMapping.from(Controller.Standard.LeftSecondaryThumb).peek().to(Controller.Actions.ContextMenu);
clickMapping.from(Controller.Standard.RightPrimaryThumb).peek().to(function (on) {
if (on && (activeHand !== Controller.Standard.RightHand)) {
toggleHand();
}
});
clickMapping.from(Controller.Standard.LeftPrimaryThumb).peek().to(function (on) {
if (on && (activeHand !== Controller.Standard.LeftHand)) {
toggleHand();
}
});
// Partial smoothed trigger is activation.
clickMapping.from(rightTrigger.partial).to(makeToggleAction(Controller.Standard.RightHand));
clickMapping.from(leftTrigger.partial).to(makeToggleAction(Controller.Standard.LeftHand));
clickMapping.enable();
// VISUAL AID -----------
// Same properties as handControllerGrab search sphere
var BALL_SIZE = 0.011;
var BALL_ALPHA = 0.5;
var fakeProjectionBall = Overlays.addOverlay("sphere", {
size: 5 * BALL_SIZE,
color: {red: 255, green: 10, blue: 10},
ignoreRayIntersection: true,
alpha: BALL_ALPHA,
visible: false,
solid: true,
drawInFront: true // Even when burried inside of something, show it.
});
var overlays = [fakeProjectionBall]; // If we want to try showing multiple balls and lasers.
Script.scriptEnding.connect(function () {
overlays.forEach(Overlays.deleteOverlay);
});
var visualizationIsShowing = false; // Not whether it desired, but simply whether it is. Just an optimziation.
function turnOffVisualization(optionalEnableClicks) { // because we're showing cursor on HUD
if (!optionalEnableClicks) {
expireMouseCursor();
}
if (!visualizationIsShowing) {
var LASER_ALPHA = 0.5;
var LASER_SEARCH_COLOR_XYZW = {x: 10 / 255, y: 10 / 255, z: 255 / 255, w: LASER_ALPHA};
var LASER_TRIGGER_COLOR_XYZW = {x: 250 / 255, y: 10 / 255, z: 10 / 255, w: LASER_ALPHA};
var SYSTEM_LASER_DIRECTION = {x: 0, y: 0, z: -1};
var systemLaserOn = false;
function clearSystemLaser() {
if (!systemLaserOn) {
return;
}
visualizationIsShowing = false;
overlays.forEach(function (overlay) {
Overlays.editOverlay(overlay, {visible: false});
});
HMD.disableHandLasers(BOTH_HUD_LASERS);
systemLaserOn = false;
}
var MAX_RAY_SCALE = 32000; // Anything large. It's a scale, not a distance.
function updateVisualization(controllerPosition, controllerDirection, hudPosition3d, hudPosition2d) {
// Show an indication of where the cursor will appear when crossing a HUD element,
// and where in-world clicking will occur.
//
// There are a number of ways we could do this, but for now, it's a blue sphere that rolls along
// the HUD surface, and a red sphere that rolls along the 3d objects that will receive the click.
// We'll leave it to other scripts (like handControllerGrab) to show a search beam when desired.
function setColoredLaser() { // answer trigger state if lasers supported, else falsey.
var color = (activeTrigger.state === 'full') ? LASER_TRIGGER_COLOR_XYZW : LASER_SEARCH_COLOR_XYZW;
return HMD.setHandLasers(activeHudLaser, true, color, SYSTEM_LASER_DIRECTION) && activeTrigger.state;
function intersection3d(position, direction) {
// Answer in-world intersection (entity or 3d overlay), or way-out point
var pickRay = {origin: position, direction: direction};
var result = findRayIntersection(pickRay);
return result.intersects ? result.intersection : Vec3.sum(position, Vec3.multiply(MAX_RAY_SCALE, direction));
}
visualizationIsShowing = true;
// We'd rather in-world interactions be done at the termination of the hand beam
// -- intersection3d(controllerPosition, controllerDirection). Maybe have handControllerGrab
// direclty manipulate both entity and 3d overlay objects.
// For now, though, we present a false projection of the cursor onto whatever is below it. This is
// different from the hand beam termination because the false projection is from the camera, while
// the hand beam termination is from the hand.
var eye = Camera.getPosition();
var falseProjection = intersection3d(eye, Vec3.subtract(hudPosition3d, eye));
Overlays.editOverlay(fakeProjectionBall, {visible: true, position: falseProjection});
Reticle.visible = false;
return visualizationIsShowing; // In case we change caller to act conditionally.
}
// MAIN OPERATIONS -----------
//
function update() {
var now = Date.now();
if (!handControllerLockOut.expired(now)) {
return turnOffVisualization();
} // Let them use mouse it in peace.
if (!Menu.isOptionChecked("First Person")) {
return turnOffVisualization();
} // What to do? menus can be behind hand!
if (!Window.hasFocus()) { // Don't mess with other apps
return turnOffVisualization();
function off() {
expireMouseCursor();
clearSystemLaser();
}
if (!handControllerLockOut.expired(now)) {
return off(); // Let them use mouse it in peace.
}
if (!Menu.isOptionChecked("First Person")) {
return off(); // What to do? menus can be behind hand!
}
if (!Window.hasFocus() || !Reticle.allowMouseCapture) {
return off(); // Don't mess with other apps or paused mouse activity
}
leftTrigger.update();
rightTrigger.update();
var controllerPose = Controller.getPoseValue(activeHand);
// Valid if any plugged-in hand controller is "on". (uncradled Hydra, green-lighted Vive...)
if (!controllerPose.valid) {
return turnOffVisualization();
} // Controller is cradled.
return off(); // Controller is cradled.
}
var controllerPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, controllerPose.translation),
MyAvatar.position);
// This gets point direction right, but if you want general quaternion it would be more complicated:
@ -371,10 +409,10 @@ function update() {
var hudPoint3d = calculateRayUICollisionPoint(controllerPosition, controllerDirection);
if (!hudPoint3d) {
// FIXME - determine if this message is useful but make it so it doesn't spam the
// log in the case that it is happening
//print('Controller is parallel to HUD');
return turnOffVisualization();
if (Menu.isOptionChecked("Overlays")) { // With our hud resetting strategy, hudPoint3d should be valid here
print('Controller is parallel to HUD'); // so let us know that our assumptions are wrong.
}
return off();
}
var hudPoint2d = overlayFromWorldPoint(hudPoint3d);
@ -383,14 +421,25 @@ function update() {
setReticlePosition(hudPoint2d);
// If there's a HUD element at the (newly moved) reticle, just make it visible and bail.
if (isPointingAtOverlay(hudPoint2d)) {
if (HMD.active) { // Doesn't hurt anything without the guard, but consider it documentation.
Reticle.depth = SPHERICAL_HUD_DISTANCE; // NOT CORRECT IF WE SWITCH TO OFFSET SPHERE!
if (HMD.active) {
Reticle.depth = hudReticleDistance();
}
Reticle.visible = true;
return turnOffVisualization(true);
if (activeTrigger.state && (!systemLaserOn || (systemLaserOn !== activeTrigger.state))) { // last=>wrong color
// If the active plugin doesn't implement hand lasers, show the mouse reticle instead.
systemLaserOn = setColoredLaser();
Reticle.visible = !systemLaserOn;
} else if ((systemLaserOn || Reticle.visible) && !activeTrigger.state) {
clearSystemLaser();
Reticle.visible = false;
}
return;
}
// We are not pointing at a HUD element (but it could be a 3d overlay).
updateVisualization(controllerPosition, controllerDirection, hudPoint3d, hudPoint2d);
if (!activeTrigger.state) {
return off(); // No trigger
}
clearSystemLaser();
Reticle.visible = false;
}
var UPDATE_INTERVAL = 50; // milliseconds. Script.update is too frequent.

View file

@ -183,7 +183,7 @@ var toolBar = (function() {
function initialize() {
toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL, "highfidelity.edit.toolbar", function(windowDimensions, toolbar) {
return {
x: windowDimensions.x / 2,
x: (windowDimensions.x / 2) + (Tool.IMAGE_WIDTH * 2),
y: windowDimensions.y
};
}, {

View file

@ -9,10 +9,6 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
Script.include([
"libraries/toolBars.js",
]);
var toolIconUrl = Script.resolvePath("assets/images/tools/");
var EXAMPLES_URL = "https://metaverse.highfidelity.com/examples";
@ -54,87 +50,21 @@ function toggleExamples() {
}
}
var toolBar = (function() {
var that = {},
toolBar,
browseExamplesButton;
var toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system");
function initialize() {
toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL, "highfidelity.examples.toolbar", function(windowDimensions, toolbar) {
return {
x: windowDimensions.x / 2,
y: windowDimensions.y
};
}, {
x: -toolWidth / 2,
y: -TOOLBAR_MARGIN_Y - toolHeight
});
browseExamplesButton = toolBar.addTool({
imageURL: toolIconUrl + "examples-01.svg",
subImage: {
x: 0,
y: Tool.IMAGE_WIDTH,
width: Tool.IMAGE_WIDTH,
height: Tool.IMAGE_HEIGHT
},
width: toolWidth,
height: toolHeight,
alpha: 0.9,
visible: true,
showButtonDown: true
});
var browseExamplesButton = toolBar.addButton({
imageURL: toolIconUrl + "examples-01.svg",
objectName: "examples",
yOffset: 50,
alpha: 0.9,
});
toolBar.showTool(browseExamplesButton, true);
}
var browseExamplesButtonDown = false;
var browseExamplesButtonDown = false;
that.mousePressEvent = function(event) {
var clickedOverlay,
url,
file;
browseExamplesButton.clicked.connect(function(){
toggleExamples();
});
if (!event.isLeftButton) {
// if another mouse button than left is pressed ignore it
return false;
}
clickedOverlay = Overlays.getOverlayAtPoint({
x: event.x,
y: event.y
});
if (browseExamplesButton === toolBar.clicked(clickedOverlay)) {
toggleExamples();
return true;
}
return false;
};
that.mouseReleaseEvent = function(event) {
var handled = false;
if (browseExamplesButtonDown) {
var clickedOverlay = Overlays.getOverlayAtPoint({
x: event.x,
y: event.y
});
}
newModelButtonDown = false;
browseExamplesButtonDown = false;
return handled;
}
that.cleanup = function() {
toolBar.cleanup();
};
initialize();
return that;
}());
Controller.mousePressEvent.connect(toolBar.mousePressEvent)
Script.scriptEnding.connect(toolBar.cleanup);
Script.scriptEnding.connect(function () {
browseExamplesButton.clicked.disconnect();
});

29
scripts/system/goto.js Normal file
View file

@ -0,0 +1,29 @@
//
// goto.js
// scripts/system/
//
// Created by Howard Stearns on 2 Jun 2016
// Copyright 2016 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
//
var toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system");
var button = toolBar.addButton({
objectName: "goto",
imageURL: Script.resolvePath("assets/images/tools/directory-01.svg"),
visible: true,
yOffset: 50,
alpha: 0.9,
});
button.clicked.connect(function(){
DialogsManager.toggleAddressBar();
});
Script.scriptEnding.connect(function () {
button.clicked.disconnect();
});

42
scripts/system/hmd.js Normal file
View file

@ -0,0 +1,42 @@
//
// hmd.js
// scripts/system/
//
// Created by Howard Stearns on 2 Jun 2016
// Copyright 2016 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
//
var headset; // The preferred headset. Default to the first one found in the following list.
var displayMenuName = "Display";
var desktopMenuItemName = "Desktop";
['OpenVR (Vive)', 'Oculus Rift'].forEach(function (name) {
if (!headset && Menu.menuItemExists(displayMenuName, name)) {
headset = name;
}
});
var toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system");
var button;
if (headset) {
button = toolBar.addButton({
objectName: "hmdToggle",
imageURL: Script.resolvePath("assets/images/tools/hmd-switch-01.svg"),
visible: true,
yOffset: 50,
alpha: 0.9,
});
button.clicked.connect(function(){
var isDesktop = Menu.isOptionChecked(desktopMenuItemName);
Menu.setIsOptionChecked(isDesktop ? headset : desktopMenuItemName, true);
});
Script.scriptEnding.connect(function () {
button.clicked.disconnect();
});
}

29
scripts/system/mute.js Normal file
View file

@ -0,0 +1,29 @@
//
// goto.js
// scripts/system/
//
// Created by Howard Stearns on 2 Jun 2016
// Copyright 2016 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
//
var toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system");
var button = toolBar.addButton({
objectName: "mute",
imageURL: Script.resolvePath("assets/images/tools/microphone.svg"),
visible: true,
alpha: 0.9,
});
button.clicked.connect(function(){
var menuItem = "Mute Microphone";
Menu.setIsOptionChecked(menuItem, !Menu.isOptionChecked(menuItem));
});
Script.scriptEnding.connect(function () {
button.clicked.disconnect();
});

Some files were not shown because too many files have changed in this diff Show more