mirror of
https://github.com/Armored-Dragon/overte.git
synced 2025-03-11 16:13:16 +01:00
commit
64771428ec
104 changed files with 4283 additions and 2722 deletions
|
@ -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" },
|
||||
|
|
105
interface/resources/icons/hud-01.svg
Normal file
105
interface/resources/icons/hud-01.svg
Normal 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 |
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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: []
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -7,7 +7,7 @@ import "../../windows"
|
|||
import "../../js/Utils.js" as Utils
|
||||
import "../models"
|
||||
|
||||
Window {
|
||||
ScrollingWindow {
|
||||
id: root
|
||||
resizable: true
|
||||
width: 516
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
151
interface/resources/qml/hifi/toolbars/Toolbar.qml
Normal file
151
interface/resources/qml/hifi/toolbars/Toolbar.qml
Normal 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;
|
||||
}
|
||||
}
|
65
interface/resources/qml/hifi/toolbars/ToolbarButton.qml
Normal file
65
interface/resources/qml/hifi/toolbars/ToolbarButton.qml
Normal 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import QtQuick 2.5
|
|||
|
||||
import "."
|
||||
|
||||
Frame {
|
||||
Item {
|
||||
id: frame
|
||||
|
||||
Item { anchors.fill: parent }
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
||||
|
|
157
interface/resources/qml/windows/ScrollingWindow.qml
Normal file
157
interface/resources/qml/windows/ScrollingWindow.qml
Normal 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 ]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
96
interface/resources/qml/windows/ToolFrame.qml
Normal file
96
interface/resources/qml/windows/ToolFrame.qml
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
115
interface/src/scripting/ToolbarScriptingInterface.cpp
Normal file
115
interface/src/scripting/ToolbarScriptingInterface.cpp
Normal 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"
|
26
interface/src/scripting/ToolbarScriptingInterface.h
Normal file
26
interface/src/scripting/ToolbarScriptingInterface.h
Normal 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
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(); };
|
||||
|
|
|
@ -32,6 +32,7 @@ class Mapping;
|
|||
using MappingPointer = std::shared_ptr<Mapping>;
|
||||
using MappingList = std::list<MappingPointer>;
|
||||
|
||||
struct Pose;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()));;
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 };
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -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.
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
49
libraries/shared/src/shared/Bilateral.h
Normal file
49
libraries/shared/src/shared/Bilateral.h
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -30,7 +30,6 @@ public:
|
|||
QString text() const;
|
||||
|
||||
public slots:
|
||||
virtual void setVisible(bool v);
|
||||
void setText(const QString& arg);
|
||||
|
||||
signals:
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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"),
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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");
|
||||
|
|
118
scripts/developer/tests/toolbarTest.js
Normal file
118
scripts/developer/tests/toolbarTest.js
Normal 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;
|
||||
}());
|
124
scripts/system/assets/images/tools/hmd-switch-01.svg
Normal file
124
scripts/system/assets/images/tools/hmd-switch-01.svg
Normal 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 |
13
scripts/system/assets/images/tools/microphone.svg
Normal file
13
scripts/system/assets/images/tools/microphone.svg
Normal 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 |
|
@ -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
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
};
|
||||
}, {
|
||||
|
|
|
@ -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
29
scripts/system/goto.js
Normal 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
42
scripts/system/hmd.js
Normal 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
29
scripts/system/mute.js
Normal 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
Loading…
Reference in a new issue