Merge remote-tracking branch 'upstream/master' into smarter_textures

This commit is contained in:
Brad Davis 2017-02-19 14:00:52 -08:00
commit 07506b0078
34 changed files with 1500 additions and 553 deletions

View file

@ -40,6 +40,7 @@ module.exports = {
"Settings": false,
"SoundCache": false,
"Stats": false,
"Tablet": false,
"TextureCache": false,
"Toolbars": false,
"Uuid": false,
@ -61,7 +62,7 @@ module.exports = {
"eqeqeq": ["error", "always"],
"indent": ["error", 4, { "SwitchCase": 1 }],
"keyword-spacing": ["error", { "before": true, "after": true }],
"max-len": ["error", 128, 4],
"max-len": ["error", 192, 4],
"new-cap": ["error"],
"no-floating-decimal": ["error"],
//"no-magic-numbers": ["error", { "ignore": [0, 1], "ignoreArrayIndexes": true }],

View file

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
x="0px"
y="0px"
viewBox="0 0 50 50"
style="enable-background:new 0 0 50 50;"
xml:space="preserve"
id="svg2"
inkscape:version="0.91 r13725"
sodipodi:docname="blank.svg"><metadata
id="metadata36"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs34" /><sodipodi:namedview
pagecolor="#ff4900"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1149"
inkscape:window-height="801"
id="namedview32"
showgrid="false"
inkscape:zoom="4.72"
inkscape:cx="25"
inkscape:cy="25"
inkscape:window-x="1336"
inkscape:window-y="519"
inkscape:window-maximized="0"
inkscape:current-layer="svg2" /><style
type="text/css"
id="style4">
.st0{fill:#FFFFFF;}
</style><g
id="Layer_2" /></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -0,0 +1,81 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="Layer_1"
x="0px"
y="0px"
viewBox="0 0 50 200.1"
style="enable-background:new 0 0 50 200.1;"
xml:space="preserve"
inkscape:version="0.91 r13725"
sodipodi:docname="empty-toolbar-button.svg"><metadata
id="metadata116"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
id="defs114" /><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1574"
inkscape:window-height="1234"
id="namedview112"
showgrid="false"
inkscape:zoom="4.717641"
inkscape:cx="-13.634838"
inkscape:cy="131.18797"
inkscape:window-x="152"
inkscape:window-y="117"
inkscape:window-maximized="0"
inkscape:current-layer="Layer_1" /><style
type="text/css"
id="style3">
.st0{fill:#414042;}
.st1{fill:#FFFFFF;}
.st2{fill:#1E1E1E;}
.st3{fill:#333333;}
</style><g
id="g6"
style="fill:#ffffff;fill-opacity:1"><g
id="g8"
style="fill:#ffffff;fill-opacity:1"><path
style="fill:#ffffff;fill-opacity:1"
inkscape:connector-curvature="0"
id="path10"
d="m 50.1,146.1 c 0,2.2 -1.8,4 -4,4 l -42,0 c -2.2,0 -4,-1.8 -4,-4 l 0,-42 c 0,-2.2 1.8,-4 4,-4 l 42,0 c 2.2,0 4,1.8 4,4 l 0,42 z"
class="st0" /></g></g><g
id="g12"><g
id="g14"><path
style="fill:#414042"
inkscape:connector-curvature="0"
id="path16"
d="m 50,196.1 c 0,2.2 -1.8,4 -4,4 l -42,0 c -2.2,0 -4,-1.8 -4,-4 l 0,-42 c 0,-2.2 1.8,-4 4,-4 l 42,0 c 2.2,0 4,1.8 4,4 l 0,42 z"
class="st0" /></g></g><g
id="g18"
style="fill:#f0f0f0;fill-opacity:1"><g
id="g20"
style="fill:#f0f0f0;fill-opacity:1"><path
style="fill:#f0f0f0;fill-opacity:1"
inkscape:connector-curvature="0"
id="path22"
d="m 50,46 c 0,2.2 -1.8,4 -4,4 L 4,50 C 1.8,50 0,48.2 0,46 L 0,4 C 0,1.8 1.8,0 4,0 l 42,0 c 2.2,0 4,1.8 4,4 l 0,42 z"
class="st1" /></g></g><g
id="g24"><path
style="fill:#1e1e1e"
inkscape:connector-curvature="0"
id="path26"
d="m 50,96.1 c 0,2.2 -1.8,4 -4,4 l -42,0 c -2.2,0 -4,-1.8 -4,-4 l 0,-42 c 0,-2.2 1.8,-4 4,-4 l 42,0 c 2.2,0 4,1.8 4,4 l 0,42 z"
class="st2" /></g></svg>

After

Width:  |  Height:  |  Size: 3 KiB

View file

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="Layer_1"
x="0px"
y="0px"
viewBox="0 0 50 50"
style="enable-background:new 0 0 50 50;"
xml:space="preserve"
sodipodi:docname="finger-paint-a.svg"
inkscape:version="0.92.0 r15299"><metadata
id="metadata28"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs26" /><sodipodi:namedview
pagecolor="#ff0000"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1054"
inkscape:window-height="851"
id="namedview24"
showgrid="false"
inkscape:zoom="8.88"
inkscape:cx="6.7004505"
inkscape:cy="25"
inkscape:window-x="120"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:current-layer="Layer_1" /><style
type="text/css"
id="style10">
.st0{fill:#FFFFFF;}
</style><g
id="Layer_2" /><g
id="g15"
style="fill:#000000;fill-opacity:1"><path
class="st0"
d="M18.3,19.6c0.7,0.1,1.2,0.5,1.4,1.1c0.4,1.3,2,5.6,2.4,6.9c2.2,0,7.9,0.1,9.9,0.1c0.2,0,0.6,0,0.8,0.1 c0.5,0.1,0.8,0.4,1.1,0.9c0.1,0.2,0.2,0.4,0.2,0.6c0.8,2.9,0.7,2,1.5,4.9c0.8,3,0.1,5.7-2.3,7.7c-1.9,1.6-3.6,2.3-5.7,1.9 c-0.6-0.1-1.2-0.3-1.8-0.5c-3.6-1.5-7.3-2.9-10.9-4.3c-0.9-0.4-1.4-1.2-1.3-2c0.1-0.9,0.9-1.5,1.8-1.6c0.1,0,0.3,0,0.4,0 c0.2,0,0.4,0.1,0.7,0.2c1.6,0.6,2.6,1.4,4.2,2c0,0,0,0,0,0c0,0,0.1,0,0.2,0c-0.1-0.2-0.1-0.4-0.2-0.6c-1.4-4.9-2.9-9.8-4.3-14.7 c-0.2-0.7-0.3-1.4,0.2-2c0.4-0.6,1-0.8,1.7-0.8C18.2,19.5,18.2,19.5,18.3,19.6 M18.9,16.3c-0.1,0-0.3,0-0.4-0.1 c-1.9-0.2-3.6,0.6-4.7,2.1c-1.5,2-0.9,4.1-0.7,4.8c0.9,3,1.8,6,2.6,9c-0.1,0-0.3,0-0.4,0c-2.5,0-4.6,1.9-5,4.3 c-0.2,1.2,0.1,2.4,0.7,3.4c0.6,1,1.5,1.7,2.7,2.2c1.2,0.5,2.4,0.9,3.6,1.4c2.4,0.9,4.9,1.9,7.3,2.9c0.9,0.4,1.7,0.6,2.5,0.7 c3.9,0.7,6.7-1.2,8.5-2.7c3.4-2.8,4.6-6.7,3.4-11.1c-0.5-1.7-0.6-2.1-0.8-2.7c-0.1-0.4-0.3-1-0.7-2.2c-0.1-0.4-0.2-0.7-0.4-1.1 c-0.7-1.5-1.8-2.5-3.4-2.7c-0.6-0.1-1.2-0.1-1.8-0.1c-1.2,0-3.4,0-5.4,0c-0.6,0-1.3-0.1-1.9-0.1c-0.2-0.6-0.5-1.3-0.7-2 c-0.4-1.1-0.7-2.1-0.9-2.6C22.3,17.9,20.8,16.7,18.9,16.3L18.9,16.3z"
id="path13"
style="fill:#000000;fill-opacity:1" /></g><path
class="st0"
d="M35.1,4.3c0.5,0,1,0,1.5,0c0.6,0,1.3-0.4,1.3-1.1c0-0.6-0.5-1.2-1.2-1.2c-0.5,0-1,0-1.5,0 c-0.6,0-1.3,0.4-1.3,1.1C33.9,3.6,34.4,4.3,35.1,4.3L35.1,4.3z"
id="path17"
style="fill:#000000;fill-opacity:1" /><path
class="st0"
d="M32,7.7c-1.2-0.8-0.9-2.1,0.1-3c1.2-1-0.6-2.6-1.7-1.6c-1,0.8-1.6,2-1.6,3.2c0,1.4,0.8,2.5,2,3.2 c0.5,0.3,1.4,0.1,1.7-0.4C32.8,8.7,32.5,8.1,32,7.7L32,7.7z"
id="path19"
style="fill:#000000;fill-opacity:1" /><path
class="st0"
d="M35.4,9.6c-1.4-0.7-2.6,1.3-1.2,1.9c1,0.5,1.8,1.2,2,2.1c0,0,0,0.2,0,0.2c0,0.1,0,0.2,0,0.3c0,0,0,0.2-0.1,0.2 c0,0,0,0,0,0.1c0,0-0.1,0.1-0.1,0.1c-0.2,0.3-0.6,0.6-1,0.8c-1.3,0.5-2.8,0.2-4.1-0.2c-1.4-0.5-2.7-1.1-4.1-1.7 c-3.2-1.3-6.6-2.6-10.1-2.5c-2.8,0.1-6.1,1.1-7.4,3.5c-1.1,2.1-0.4,4.5,1.4,5.8c0-0.8,0.4-1.7,0.9-2.3c-0.7-0.9-0.7-2.2,0.3-3.1 c1.9-1.9,5.3-1.9,7.8-1.4c3.1,0.6,5.9,2,8.8,3.2c2.8,1.1,6.5,2.1,9-0.2C39.9,14.2,38,10.8,35.4,9.6z"
id="path21"
style="fill:#000000;fill-opacity:1" /></svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

View file

@ -0,0 +1,30 @@
<?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 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<g id="Layer_2">
</g>
<g>
<path class="st0" d="M18.3,19.6c0.7,0.1,1.2,0.5,1.4,1.1c0.4,1.3,2,5.6,2.4,6.9c2.2,0,7.9,0.1,9.9,0.1c0.2,0,0.6,0,0.8,0.1
c0.5,0.1,0.8,0.4,1.1,0.9c0.1,0.2,0.2,0.4,0.2,0.6c0.8,2.9,0.7,2,1.5,4.9c0.8,3,0.1,5.7-2.3,7.7c-1.9,1.6-3.6,2.3-5.7,1.9
c-0.6-0.1-1.2-0.3-1.8-0.5c-3.6-1.5-7.3-2.9-10.9-4.3c-0.9-0.4-1.4-1.2-1.3-2c0.1-0.9,0.9-1.5,1.8-1.6c0.1,0,0.3,0,0.4,0
c0.2,0,0.4,0.1,0.7,0.2c1.6,0.6,2.6,1.4,4.2,2c0,0,0,0,0,0c0,0,0.1,0,0.2,0c-0.1-0.2-0.1-0.4-0.2-0.6c-1.4-4.9-2.9-9.8-4.3-14.7
c-0.2-0.7-0.3-1.4,0.2-2c0.4-0.6,1-0.8,1.7-0.8C18.2,19.5,18.2,19.5,18.3,19.6 M18.9,16.3c-0.1,0-0.3,0-0.4-0.1
c-1.9-0.2-3.6,0.6-4.7,2.1c-1.5,2-0.9,4.1-0.7,4.8c0.9,3,1.8,6,2.6,9c-0.1,0-0.3,0-0.4,0c-2.5,0-4.6,1.9-5,4.3
c-0.2,1.2,0.1,2.4,0.7,3.4c0.6,1,1.5,1.7,2.7,2.2c1.2,0.5,2.4,0.9,3.6,1.4c2.4,0.9,4.9,1.9,7.3,2.9c0.9,0.4,1.7,0.6,2.5,0.7
c3.9,0.7,6.7-1.2,8.5-2.7c3.4-2.8,4.6-6.7,3.4-11.1c-0.5-1.7-0.6-2.1-0.8-2.7c-0.1-0.4-0.3-1-0.7-2.2c-0.1-0.4-0.2-0.7-0.4-1.1
c-0.7-1.5-1.8-2.5-3.4-2.7c-0.6-0.1-1.2-0.1-1.8-0.1c-1.2,0-3.4,0-5.4,0c-0.6,0-1.3-0.1-1.9-0.1c-0.2-0.6-0.5-1.3-0.7-2
c-0.4-1.1-0.7-2.1-0.9-2.6C22.3,17.9,20.8,16.7,18.9,16.3L18.9,16.3z"/>
</g>
<path class="st0" d="M35.1,4.3c0.5,0,1,0,1.5,0c0.6,0,1.3-0.4,1.3-1.1c0-0.6-0.5-1.2-1.2-1.2c-0.5,0-1,0-1.5,0
c-0.6,0-1.3,0.4-1.3,1.1C33.9,3.6,34.4,4.3,35.1,4.3L35.1,4.3z"/>
<path class="st0" d="M32,7.7c-1.2-0.8-0.9-2.1,0.1-3c1.2-1-0.6-2.6-1.7-1.6c-1,0.8-1.6,2-1.6,3.2c0,1.4,0.8,2.5,2,3.2
c0.5,0.3,1.4,0.1,1.7-0.4C32.8,8.7,32.5,8.1,32,7.7L32,7.7z"/>
<path class="st0" d="M35.4,9.6c-1.4-0.7-2.6,1.3-1.2,1.9c1,0.5,1.8,1.2,2,2.1c0,0,0,0.2,0,0.2c0,0.1,0,0.2,0,0.3c0,0,0,0.2-0.1,0.2
c0,0,0,0,0,0.1c0,0-0.1,0.1-0.1,0.1c-0.2,0.3-0.6,0.6-1,0.8c-1.3,0.5-2.8,0.2-4.1-0.2c-1.4-0.5-2.7-1.1-4.1-1.7
c-3.2-1.3-6.6-2.6-10.1-2.5c-2.8,0.1-6.1,1.1-7.4,3.5c-1.1,2.1-0.4,4.5,1.4,5.8c0-0.8,0.4-1.7,0.9-2.3c-0.7-0.9-0.7-2.2,0.3-3.1
c1.9-1.9,5.3-1.9,7.8-1.4c3.1,0.6,5.9,2,8.8,3.2c2.8,1.1,6.5,2.1,9-0.2C39.9,14.2,38,10.8,35.4,9.6z"/>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -48,7 +48,16 @@ OriginalDesktop.Desktop {
// This used to create sysToolbar dynamically with a call to getToolbar() within onCompleted.
// Beginning with QT 5.6, this stopped working, as anything added to toolbars too early got
// wiped during startup.
Toolbar {
id: sysToolbar;
objectName: "com.highfidelity.interface.toolbar.system";
anchors.horizontalCenter: settings.constrainToolbarToCenterX ? desktop.horizontalCenter : undefined;
// Literal 50 is overwritten by settings from previous session, and sysToolbar.x comes from settings when not constrained.
x: sysToolbar.x
y: 50
shown: false
}
Settings {
id: settings;
category: "toolbar";
@ -58,8 +67,9 @@ OriginalDesktop.Desktop {
settings.constrainToolbarToCenterX = constrain;
}
property var toolbars: (function (map) { // answer dictionary preloaded with sysToolbar
return map; })({});
map[sysToolbar.objectName] = sysToolbar;
return map;
})({});
Component.onCompleted: {
WebEngine.settings.javascriptCanOpenWindows = true;

View file

@ -97,10 +97,12 @@ FocusScope {
menuPopperUpper.closeLastMenu();
}
function setRootMenu(menu) {
tabletMenu.rootMenu = menu
function setRootMenu(rootMenu, subMenu) {
tabletMenu.subMenu = subMenu;
tabletMenu.rootMenu = rootMenu;
buildMenu()
}
function buildMenu() {
// Build submenu if specified.
if (subMenu !== "") {

View file

@ -83,7 +83,7 @@ FocusScope {
}
function recalcSize() {
if (model.count !== count || !visible) {
if (!model || model.count !== count || !visible) {
return;
}

View file

@ -6,7 +6,9 @@ Item {
objectName: "tabletRoot"
property string username: "Unknown user"
property var eventBridge;
property string option: ""
property var rootMenu;
property string subMenu: ""
signal showDesktop();
@ -14,7 +16,13 @@ Item {
option = value;
}
function setMenuProperties(rootMenu, subMenu) {
tabletRoot.rootMenu = rootMenu;
tabletRoot.subMenu = subMenu;
}
function loadSource(url) {
loader.source = ""; // make sure we load the qml fresh each time.
loader.source = url;
}
@ -77,13 +85,15 @@ Item {
if (loader.item.hasOwnProperty("sendToScript")) {
loader.item.sendToScript.connect(tabletRoot.sendToScript);
}
if (loader.item.hasOwnProperty("subMenu")) {
loader.item.subMenu = option;
if (loader.item.hasOwnProperty("setRootMenu")) {
loader.item.setRootMenu(tabletRoot.rootMenu, tabletRoot.subMenu);
}
loader.item.forceActiveFocus();
}
}
width: 480
height: 720
height: 706
function setShown(value) {}
}

View file

@ -0,0 +1,111 @@
//
// WindowRoot.qml
//
// Created by Anthony Thibault on 14 Feb 2017
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// This qml is used when tablet content is shown on the 2d overlay ui
// TODO: FIXME: this is practically identical to TabletRoot.qml
import "../../windows" as Windows
import QtQuick 2.0
import Hifi 1.0
Windows.ScrollingWindow {
id: tabletRoot
objectName: "tabletRoot"
property string username: "Unknown user"
property var eventBridge;
property var rootMenu;
property string subMenu: ""
shown: false
resizable: false
signal showDesktop();
function setMenuProperties(rootMenu, subMenu) {
tabletRoot.rootMenu = rootMenu;
tabletRoot.subMenu = subMenu;
}
function loadSource(url) {
loader.source = ""; // make sure we load the qml fresh each time.
loader.source = url;
}
function loadWebUrl(url, injectedJavaScriptUrl) {
loader.item.url = url;
loader.item.scriptURL = injectedJavaScriptUrl;
}
// used to send a message from qml to interface script.
signal sendToScript(var message);
// used to receive messages from interface script
function fromScript(message) {
if (loader.item.hasOwnProperty("fromScript")) {
loader.item.fromScript(message);
}
}
SoundEffect {
id: buttonClickSound
volume: 0.1
source: "../../../sounds/Gamemaster-Audio-button-click.wav"
}
function playButtonClickSound() {
// Because of the asynchronous nature of initalization, it is possible for this function to be
// called before the C++ has set the globalPosition context variable.
if (typeof globalPosition !== 'undefined') {
buttonClickSound.play(globalPosition);
}
}
function toggleMicEnabled() {
ApplicationInterface.toggleMuteAudio();
}
function setUsername(newUsername) {
username = newUsername;
}
Loader {
id: loader
objectName: "loader"
asynchronous: false
height: pane.scrollHeight
width: pane.contentWidth
anchors.left: parent.left
anchors.top: parent.top
onLoaded: {
if (loader.item.hasOwnProperty("eventBridge")) {
loader.item.eventBridge = eventBridge;
// Hook up callback for clara.io download from the marketplace.
eventBridge.webEventReceived.connect(function (event) {
if (event.slice(0, 17) === "CLARA.IO DOWNLOAD") {
ApplicationInterface.addAssetToWorldFromURL(event.slice(18));
}
});
}
if (loader.item.hasOwnProperty("sendToScript")) {
loader.item.sendToScript.connect(tabletRoot.sendToScript);
}
if (loader.item.hasOwnProperty("setRootMenu")) {
loader.item.setRootMenu(tabletRoot.rootMenu, tabletRoot.subMenu);
}
loader.item.forceActiveFocus();
}
}
implicitWidth: 480
implicitHeight: 706
}

View file

@ -29,6 +29,7 @@ Item {
id: image
y: -parent.yOffset;
width: parent.width
source: "../../../icons/tablet-icons/empty-toolbar-button.svg"
}
}

View file

@ -25,7 +25,7 @@ Window {
property real buttonSize: 50;
property var buttons: []
property var container: horizontal ? row : column
Settings {
category: "toolbar/" + window.objectName
property alias x: window.x
@ -49,6 +49,7 @@ Window {
id: content
implicitHeight: horizontal ? row.height : column.height
implicitWidth: horizontal ? row.width : column.width
property bool wasVisibleBeforeBeingPinned: false
Row {
id: row
@ -65,19 +66,11 @@ Window {
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;
}
if (desktop.pinned) {
content.wasVisibleBeforeBeingPinned = window.visible;
window.visible = false;
} else {
window.visible = content.wasVisibleBeforeBeingPinned;
}
}
}
@ -106,6 +99,24 @@ Window {
return buttons[index];
}
function sortButtons() {
var children = [];
for (var i = 0; i < container.children.length; i++) {
children[i] = container.children[i];
}
children.sort(function (a, b) {
if (a.sortOrder === b.sortOrder) {
// subsort by stableOrder, because JS sort is not stable in qml.
return a.stableOrder - b.stableOrder;
} else {
return a.sortOrder - b.sortOrder;
}
});
container.children = children;
}
function addButton(properties) {
properties = properties || {}
@ -123,8 +134,12 @@ Window {
properties.opacity = 0;
result = toolbarButtonBuilder.createObject(container, properties);
buttons.push(result);
result.opacity = 1;
updatePinned();
sortButtons();
return result;
}
@ -137,6 +152,10 @@ Window {
buttons[index].destroy();
buttons.splice(index, 1);
updatePinned();
if (buttons.length === 0) {
visible = false;
}
}
function updatePinned() {

View file

@ -11,12 +11,33 @@ StateImage {
property int imageOnOut: 0
property int imageOnIn: 2
property string text: ""
property string hoverText: button.text
property string activeText: button.text
property string activeHoverText: button.activeText
property string icon: "icons/tablet-icons/blank.svg"
property string hoverIcon: button.icon
property string activeIcon: button.icon
property string activeHoverIcon: button.activeIcon
property int sortOrder: 100
property int stableSortOrder: 0
signal clicked()
function changeProperty(key, value) {
button[key] = value;
}
function urlHelper(src) {
if (src.match(/\bhttp/)) {
return src;
} else {
return "../../../" + src;
}
}
function updateState() {
if (!button.isEntered && !button.isActive) {
buttonState = imageOffOut;
@ -38,7 +59,7 @@ StateImage {
running: false
onTriggered: button.clicked();
}
MouseArea {
id: mouseArea
hoverEnabled: true
@ -53,5 +74,28 @@ StateImage {
updateState();
}
}
Image {
id: icon
width: 28
height: 28
anchors.bottom: caption.top
anchors.bottomMargin: 0
anchors.horizontalCenter: parent.horizontalCenter
fillMode: Image.Stretch
source: urlHelper(button.isActive ? (button.isEntered ? button.activeHoverIcon : button.activeIcon) : (button.isEntered ? button.hoverIcon : button.icon))
}
Text {
id: caption
color: button.isActive ? "#000000" : "#ffffff"
text: button.isActive ? (button.isEntered ? button.activeHoverText : button.activeText) : (button.isEntered ? button.hoverText : button.text)
font.bold: false
font.pixelSize: 9
anchors.bottom: parent.bottom
anchors.bottomMargin: 5
anchors.horizontalCenter: parent.horizontalCenter
horizontalAlignment: Text.AlignHCenter
}
}

View file

@ -85,6 +85,10 @@ Fadable {
function setDefaultFocus() {} // Default function; can be overridden by dialogs.
function setShown(value) {
window.shown = value;
}
property var rectifier: Timer {
property bool executing: false;
interval: 100

View file

@ -545,6 +545,8 @@ Setting::Handle<int> sessionRunTime{ "sessionRunTime", 0 };
const float DEFAULT_HMD_TABLET_SCALE_PERCENT = 100.0f;
const float DEFAULT_DESKTOP_TABLET_SCALE_PERCENT = 75.0f;
const bool DEFAULT_DESKTOP_TABLET_BECOMES_TOOLBAR = true;
const bool DEFAULT_HMD_TABLET_BECOMES_TOOLBAR = false;
Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bool runServer, QString runServerPathOption) :
QApplication(argc, argv),
@ -565,6 +567,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
_fieldOfView("fieldOfView", DEFAULT_FIELD_OF_VIEW_DEGREES),
_hmdTabletScale("hmdTabletScale", DEFAULT_HMD_TABLET_SCALE_PERCENT),
_desktopTabletScale("desktopTabletScale", DEFAULT_DESKTOP_TABLET_SCALE_PERCENT),
_desktopTabletBecomesToolbarSetting("desktopTabletBecomesToolbar", DEFAULT_DESKTOP_TABLET_BECOMES_TOOLBAR),
_hmdTabletBecomesToolbarSetting("hmdTabletBecomesToolbar", DEFAULT_HMD_TABLET_BECOMES_TOOLBAR),
_constrainToolbarPosition("toolbar/constrainToolbarToCenterX", true),
_scaleMirror(1.0f),
_rotateMirror(0.0f),
@ -831,6 +835,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
connect(this, &QCoreApplication::aboutToQuit, addressManager.data(), &AddressManager::storeCurrentAddress);
connect(this, &Application::activeDisplayPluginChanged, this, &Application::updateThreadPoolCount);
connect(this, &Application::activeDisplayPluginChanged, this, &Application::updateSystemTabletMode);
// Save avatar location immediately after a teleport.
connect(myAvatar.get(), &MyAvatar::positionGoneTo,
@ -1537,6 +1542,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
connect(this, &QCoreApplication::aboutToQuit, this, &Application::addAssetToWorldMessageClose);
connect(&domainHandler, &DomainHandler::hostnameChanged, this, &Application::addAssetToWorldMessageClose);
updateSystemTabletMode();
}
void Application::domainConnectionRefused(const QString& reasonMessage, int reasonCodeInt, const QString& extraInfo) {
@ -2330,6 +2337,16 @@ void Application::setDesktopTabletScale(float desktopTabletScale) {
_desktopTabletScale.set(desktopTabletScale);
}
void Application::setDesktopTabletBecomesToolbarSetting(bool value) {
_desktopTabletBecomesToolbarSetting.set(value);
updateSystemTabletMode();
}
void Application::setHmdTabletBecomesToolbarSetting(bool value) {
_hmdTabletBecomesToolbarSetting.set(value);
updateSystemTabletMode();
}
void Application::setSettingConstrainToolbarPosition(bool setting) {
_constrainToolbarPosition.set(setting);
DependencyManager::get<OffscreenUi>()->setConstrainToolbarToCenterX(setting);
@ -5462,6 +5479,8 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
scriptEngine->registerGlobalObject("Desktop", DependencyManager::get<DesktopScriptingInterface>().data());
scriptEngine->registerGlobalObject("Toolbars", DependencyManager::get<ToolbarScriptingInterface>().data());
DependencyManager::get<TabletScriptingInterface>().data()->setToolbarScriptingInterface(DependencyManager::get<ToolbarScriptingInterface>().data());
scriptEngine->registerGlobalObject("Window", DependencyManager::get<WindowScriptingInterface>().data());
qScriptRegisterMetaType(scriptEngine, CustomPromptResultToScriptValue, CustomPromptResultFromScriptValue);
scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter,
@ -6679,6 +6698,12 @@ void Application::updateDisplayMode() {
}
emit activeDisplayPluginChanged();
if (_displayPlugin->isHmd()) {
qCDebug(interfaceapp) << "Entering into HMD Mode";
} else {
qCDebug(interfaceapp) << "Entering into Desktop Mode";
}
// reset the avatar, to set head and hand palms back to a reasonable default pose.
getMyAvatar()->reset(false);
@ -6854,6 +6879,14 @@ void Application::updateThreadPoolCount() const {
QThreadPool::globalInstance()->setMaxThreadCount(threadPoolSize);
}
void Application::updateSystemTabletMode() {
if (isHMDMode()) {
DependencyManager::get<TabletScriptingInterface>()->setToolbarMode(getHmdTabletBecomesToolbarSetting());
} else {
DependencyManager::get<TabletScriptingInterface>()->setToolbarMode(getDesktopTabletBecomesToolbarSetting());
}
}
void Application::toggleMuteAudio() {
auto menu = Menu::getInstance();
menu->setIsOptionChecked(MenuOption::MuteAudio, !menu->isOptionChecked(MenuOption::MuteAudio));

View file

@ -214,6 +214,11 @@ public:
float getDesktopTabletScale() { return _desktopTabletScale.get(); }
void setDesktopTabletScale(float desktopTabletScale);
bool getDesktopTabletBecomesToolbarSetting() { return _desktopTabletBecomesToolbarSetting.get(); }
void setDesktopTabletBecomesToolbarSetting(bool value);
bool getHmdTabletBecomesToolbarSetting() { return _hmdTabletBecomesToolbarSetting.get(); }
void setHmdTabletBecomesToolbarSetting(bool value);
float getSettingConstrainToolbarPosition() { return _constrainToolbarPosition.get(); }
void setSettingConstrainToolbarPosition(bool setting);
@ -310,6 +315,7 @@ public slots:
bool exportEntities(const QString& filename, float x, float y, float z, float scale);
bool importEntities(const QString& url);
void updateThreadPoolCount() const;
void updateSystemTabletMode();
static void setLowVelocityFilter(bool lowVelocityFilter);
Q_INVOKABLE void loadDialog();
@ -550,6 +556,8 @@ private:
Setting::Handle<float> _fieldOfView;
Setting::Handle<float> _hmdTabletScale;
Setting::Handle<float> _desktopTabletScale;
Setting::Handle<bool> _desktopTabletBecomesToolbarSetting;
Setting::Handle<bool> _hmdTabletBecomesToolbarSetting;
Setting::Handle<bool> _constrainToolbarPosition;
float _scaleMirror;

View file

@ -92,6 +92,16 @@ void setupPreferences() {
preference->setMax(500);
preferences->addPreference(preference);
}
{
auto getter = []()->bool { return qApp->getDesktopTabletBecomesToolbarSetting(); };
auto setter = [](bool value) { qApp->setDesktopTabletBecomesToolbarSetting(value); };
preferences->addPreference(new CheckPreference(UI_CATEGORY, "Desktop Tablet Becomes Toolbar", getter, setter));
}
{
auto getter = []()->bool { return qApp->getHmdTabletBecomesToolbarSetting(); };
auto setter = [](bool value) { qApp->setHmdTabletBecomesToolbarSetting(value); };
preferences->addPreference(new CheckPreference(UI_CATEGORY, "HMD Tablet Becomes Toolbar", getter, setter));
}
// Snapshots
static const QString SNAPSHOTS { "Snapshots" };

View file

@ -5,9 +5,6 @@
#include <AudioInjector.h>
SoundEffect::~SoundEffect() {
if (_sound) {
_sound->deleteLater();
}
if (_injector) {
// stop will cause the AudioInjector to delete itself.
_injector->stop();

View file

@ -11,17 +11,36 @@
#include <QtCore/QThread>
#include <AccountManager.h>
#include "DependencyManager.h"
#include <PathUtils.h>
#include <QmlWindowClass.h>
#include <QQmlProperty>
#include <RegisteredMetaTypes.h>
#include "ScriptEngineLogging.h"
#include "DependencyManager.h"
#include "OffscreenUi.h"
#include <OffscreenUi.h>
#include <InfoView.h>
#include "SoundEffect.h"
TabletScriptingInterface::TabletScriptingInterface() {
qmlRegisterType<SoundEffect>("Hifi", 1, 0, "SoundEffect");
}
QObject* TabletScriptingInterface::getSystemToolbarProxy() {
const QString SYSTEM_TOOLBAR = "com.highfidelity.interface.toolbar.system";
Qt::ConnectionType connectionType = Qt::AutoConnection;
if (QThread::currentThread() != _toolbarScriptingInterface->thread()) {
connectionType = Qt::BlockingQueuedConnection;
}
QObject* toolbarProxy = nullptr;
bool hasResult = QMetaObject::invokeMethod(_toolbarScriptingInterface, "getToolbar", connectionType, Q_RETURN_ARG(QObject*, toolbarProxy), Q_ARG(QString, SYSTEM_TOOLBAR));
if (hasResult) {
return toolbarProxy;
} else {
qCWarning(scriptengine) << "ToolbarScriptingInterface getToolbar has no result";
return nullptr;
}
}
QObject* TabletScriptingInterface::getTablet(const QString& tabletId) {
std::lock_guard<std::mutex> guard(_mutex);
@ -35,10 +54,21 @@ QObject* TabletScriptingInterface::getTablet(const QString& tabletId) {
// allocate a new tablet, add it to the map then return it.
auto tabletProxy = QSharedPointer<TabletProxy>(new TabletProxy(tabletId));
_tabletProxies[tabletId] = tabletProxy;
tabletProxy->setToolbarMode(_toolbarMode);
return tabletProxy.data();
}
}
void TabletScriptingInterface::setToolbarMode(bool toolbarMode) {
std::lock_guard<std::mutex> guard(_mutex);
_toolbarMode = toolbarMode;
for (auto& iter : _tabletProxies) {
iter.second->setToolbarMode(toolbarMode);
}
}
void TabletScriptingInterface::setQmlTabletRoot(QString tabletId, QQuickItem* qmlTabletRoot, QObject* qmlOffscreenSurface) {
TabletProxy* tablet = qobject_cast<TabletProxy*>(getTablet(tabletId));
if (tablet) {
@ -141,8 +171,51 @@ static const char* TABLET_SOURCE_URL = "Tablet.qml";
static const char* WEB_VIEW_SOURCE_URL = "TabletWebView.qml";
static const char* VRMENU_SOURCE_URL = "TabletMenu.qml";
class TabletRootWindow : public QmlWindowClass {
virtual QString qmlSource() const { return "hifi/tablet/WindowRoot.qml"; }
};
TabletProxy::TabletProxy(QString name) : _name(name) {
;
}
void TabletProxy::setToolbarMode(bool toolbarMode) {
if (toolbarMode == _toolbarMode) {
return;
}
_toolbarMode = toolbarMode;
if (toolbarMode) {
removeButtonsFromHomeScreen();
addButtonsToToolbar();
// create new desktop window
auto offscreenUi = DependencyManager::get<OffscreenUi>();
offscreenUi->executeOnUiThread([=] {
auto tabletRootWindow = new TabletRootWindow();
tabletRootWindow->initQml(QVariantMap());
auto quickItem = tabletRootWindow->asQuickItem();
_desktopWindow = tabletRootWindow;
QMetaObject::invokeMethod(quickItem, "setShown", Q_ARG(const QVariant&, QVariant(false)));
QObject::connect(quickItem, SIGNAL(windowClosed()), this, SLOT(desktopWindowClosed()));
QObject::connect(tabletRootWindow, SIGNAL(webEventReceived(QVariant)), this, SIGNAL(webEventReceived(QVariant)));
// forward qml surface events to interface js
connect(tabletRootWindow, &QmlWindowClass::fromQml, this, &TabletProxy::fromQml);
});
} else {
removeButtonsFromToolbar();
addButtonsToHomeScreen();
// destroy desktop window
if (_desktopWindow) {
_desktopWindow->deleteLater();
_desktopWindow = nullptr;
}
}
}
static void addButtonProxyToQmlTablet(QQuickItem* qmlTablet, TabletButtonProxy* buttonProxy) {
@ -195,6 +268,13 @@ void TabletProxy::setQmlTabletRoot(QQuickItem* qmlTabletRoot, QObject* qmlOffscr
}
});
if (_toolbarMode) {
// if someone creates the tablet in toolbar mode, make sure to display the home screen on the tablet.
auto loader = _qmlTabletRoot->findChild<QQuickItem*>("loader");
QObject::connect(loader, SIGNAL(loaded()), this, SLOT(addButtonsToHomeScreen()), Qt::DirectConnection);
QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, QVariant(TABLET_SOURCE_URL)));
}
gotoHomeScreen();
QMetaObject::invokeMethod(_qmlTabletRoot, "setUsername", Q_ARG(const QVariant&, QVariant(getUsername())));
@ -214,39 +294,61 @@ void TabletProxy::setQmlTabletRoot(QQuickItem* qmlTabletRoot, QObject* qmlOffscr
}
void TabletProxy::gotoMenuScreen(const QString& submenu) {
if (_qmlTabletRoot) {
if (_state != State::Menu) {
removeButtonsFromHomeScreen();
QMetaObject::invokeMethod(_qmlTabletRoot, "setOption", Q_ARG(const QVariant&, QVariant(submenu)));
auto loader = _qmlTabletRoot->findChild<QQuickItem*>("loader");
QObject::connect(loader, SIGNAL(loaded()), this, SLOT(addButtonsToMenuScreen()), Qt::DirectConnection);
QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, QVariant(VRMENU_SOURCE_URL)));
_state = State::Menu;
emit screenChanged(QVariant("Menu"), QVariant(VRMENU_SOURCE_URL));
}
QObject* root = nullptr;
if (!_toolbarMode && _qmlTabletRoot) {
root = _qmlTabletRoot;
} else if (_toolbarMode && _desktopWindow) {
root = _desktopWindow->asQuickItem();
}
if (root) {
removeButtonsFromHomeScreen();
auto offscreenUi = DependencyManager::get<OffscreenUi>();
QObject* menu = offscreenUi->getRootMenu();
QMetaObject::invokeMethod(root, "setMenuProperties", Q_ARG(QVariant, QVariant::fromValue(menu)), Q_ARG(const QVariant&, QVariant(submenu)));
QMetaObject::invokeMethod(root, "loadSource", Q_ARG(const QVariant&, QVariant(VRMENU_SOURCE_URL)));
_state = State::Menu;
emit screenChanged(QVariant("Menu"), QVariant(VRMENU_SOURCE_URL));
QMetaObject::invokeMethod(root, "setShown", Q_ARG(const QVariant&, QVariant(true)));
}
}
void TabletProxy::loadQMLSource(const QVariant& path) {
if (_qmlTabletRoot) {
QObject* root = nullptr;
if (!_toolbarMode && _qmlTabletRoot) {
root = _qmlTabletRoot;
} else if (_toolbarMode && _desktopWindow) {
root = _desktopWindow->asQuickItem();
}
if (root) {
if (_state != State::QML) {
removeButtonsFromHomeScreen();
QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, path));
QMetaObject::invokeMethod(root, "loadSource", Q_ARG(const QVariant&, path));
_state = State::QML;
emit screenChanged(QVariant("QML"), path);
QMetaObject::invokeMethod(root, "setShown", Q_ARG(const QVariant&, QVariant(true)));
}
}
}
void TabletProxy::gotoHomeScreen() {
if (_qmlTabletRoot) {
if (_state != State::Home) {
if (_state != State::Home) {
if (!_toolbarMode && _qmlTabletRoot) {
auto loader = _qmlTabletRoot->findChild<QQuickItem*>("loader");
QObject::connect(loader, SIGNAL(loaded()), this, SLOT(addButtonsToHomeScreen()), Qt::DirectConnection);
QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, QVariant(TABLET_SOURCE_URL)));
QMetaObject::invokeMethod(_qmlTabletRoot, "playButtonClickSound");
_state = State::Home;
emit screenChanged(QVariant("Home"), QVariant(TABLET_SOURCE_URL));
} else if (_toolbarMode && _desktopWindow) {
// close desktop window
if (_desktopWindow->asQuickItem()) {
QMetaObject::invokeMethod(_desktopWindow->asQuickItem(), "setShown", Q_ARG(const QVariant&, QVariant(false)));
}
}
_state = State::Home;
emit screenChanged(QVariant("Home"), QVariant(TABLET_SOURCE_URL));
}
}
@ -255,31 +357,52 @@ void TabletProxy::gotoWebScreen(const QString& url) {
}
void TabletProxy::gotoWebScreen(const QString& url, const QString& injectedJavaScriptUrl) {
if (_qmlTabletRoot) {
if (_state == State::Home) {
removeButtonsFromHomeScreen();
}
if (_state != State::Web) {
QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, QVariant(WEB_VIEW_SOURCE_URL)));
_state = State::Web;
emit screenChanged(QVariant("Web"), QVariant(url));
}
QMetaObject::invokeMethod(_qmlTabletRoot, "loadWebUrl", Q_ARG(const QVariant&, QVariant(url)),
Q_ARG(const QVariant&, QVariant(injectedJavaScriptUrl)));
QObject* root = nullptr;
if (!_toolbarMode && _qmlTabletRoot) {
root = _qmlTabletRoot;
} else if (_toolbarMode && _desktopWindow) {
root = _desktopWindow->asQuickItem();
}
if (root) {
QMetaObject::invokeMethod(root, "loadSource", Q_ARG(const QVariant&, QVariant(WEB_VIEW_SOURCE_URL)));
QMetaObject::invokeMethod(root, "setShown", Q_ARG(const QVariant&, QVariant(true)));
QMetaObject::invokeMethod(root, "loadWebUrl", Q_ARG(const QVariant&, QVariant(url)), Q_ARG(const QVariant&, QVariant(injectedJavaScriptUrl)));
}
_state = State::Web;
emit screenChanged(QVariant("Web"), QVariant(url));
}
QObject* TabletProxy::addButton(const QVariant& properties) {
auto tabletButtonProxy = QSharedPointer<TabletButtonProxy>(new TabletButtonProxy(properties.toMap()));
std::lock_guard<std::mutex> guard(_mutex);
_tabletButtonProxies.push_back(tabletButtonProxy);
if (_qmlTabletRoot) {
if (!_toolbarMode && _qmlTabletRoot) {
auto tablet = getQmlTablet();
if (tablet) {
addButtonProxyToQmlTablet(tablet, tabletButtonProxy.data());
} else {
qCCritical(scriptengine) << "Could not find tablet in TabletRoot.qml";
}
} else if (_toolbarMode) {
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
QObject* toolbarProxy = tabletScriptingInterface->getSystemToolbarProxy();
Qt::ConnectionType connectionType = Qt::AutoConnection;
if (QThread::currentThread() != toolbarProxy->thread()) {
connectionType = Qt::BlockingQueuedConnection;
}
// copy properties from tablet button proxy to toolbar button proxy.
QObject* toolbarButtonProxy = nullptr;
bool hasResult = QMetaObject::invokeMethod(toolbarProxy, "addButton", connectionType, Q_RETURN_ARG(QObject*, toolbarButtonProxy), Q_ARG(QVariant, tabletButtonProxy->getProperties()));
if (hasResult) {
tabletButtonProxy->setToolbarButtonProxy(toolbarButtonProxy);
} else {
qCWarning(scriptengine) << "ToolbarProxy addButton has no result";
}
}
return tabletButtonProxy.data();
}
@ -298,11 +421,18 @@ void TabletProxy::removeButton(QObject* tabletButtonProxy) {
auto iter = std::find(_tabletButtonProxies.begin(), _tabletButtonProxies.end(), tabletButtonProxy);
if (iter != _tabletButtonProxies.end()) {
if (_qmlTabletRoot) {
if (!_toolbarMode && _qmlTabletRoot) {
(*iter)->setQmlButton(nullptr);
if (tablet) {
QMetaObject::invokeMethod(tablet, "removeButtonProxy", Qt::AutoConnection, Q_ARG(QVariant, (*iter)->getProperties()));
}
} else if (_toolbarMode) {
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
QObject* toolbarProxy = tabletScriptingInterface->getSystemToolbarProxy();
// remove button from toolbarProxy
QMetaObject::invokeMethod(toolbarProxy, "removeButton", Qt::AutoConnection, Q_ARG(QVariant, (*iter)->getUuid().toString()));
(*iter)->setToolbarButtonProxy(nullptr);
}
_tabletButtonProxies.erase(iter);
} else {
@ -329,20 +459,24 @@ void TabletProxy::updateAudioBar(const double micLevel) {
}
void TabletProxy::emitScriptEvent(QVariant msg) {
if (_qmlOffscreenSurface) {
if (!_toolbarMode && _qmlOffscreenSurface) {
QMetaObject::invokeMethod(_qmlOffscreenSurface, "emitScriptEvent", Qt::AutoConnection, Q_ARG(QVariant, msg));
} else if (_toolbarMode && _desktopWindow) {
QMetaObject::invokeMethod(_desktopWindow, "emitScriptEvent", Qt::AutoConnection, Q_ARG(QVariant, msg));
}
}
void TabletProxy::sendToQml(QVariant msg) {
if (_qmlOffscreenSurface) {
if (!_toolbarMode && _qmlOffscreenSurface) {
QMetaObject::invokeMethod(_qmlOffscreenSurface, "sendToQml", Qt::AutoConnection, Q_ARG(QVariant, msg));
} else if (_toolbarMode && _desktopWindow) {
QMetaObject::invokeMethod(_desktopWindow, "sendToQml", Qt::AutoConnection, Q_ARG(QVariant, msg));
}
}
void TabletProxy::addButtonsToHomeScreen() {
auto tablet = getQmlTablet();
if (!tablet) {
if (!tablet || _toolbarMode) {
return;
}
@ -358,30 +492,51 @@ QObject* TabletProxy::getTabletSurface() {
return _qmlOffscreenSurface;
}
void TabletProxy::addButtonsToMenuScreen() {
if (!_qmlTabletRoot) {
return;
void TabletProxy::removeButtonsFromHomeScreen() {
auto tablet = getQmlTablet();
for (auto& buttonProxy : _tabletButtonProxies) {
if (tablet) {
QMetaObject::invokeMethod(tablet, "removeButtonProxy", Qt::AutoConnection, Q_ARG(QVariant, buttonProxy->getProperties()));
}
buttonProxy->setQmlButton(nullptr);
}
auto loader = _qmlTabletRoot->findChild<QQuickItem*>("loader");
if (!loader) {
return;
}
QQuickItem* VrMenu = loader->findChild<QQuickItem*>("tabletMenu");
if (VrMenu) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
QObject* menu = offscreenUi->getRootMenu();
QMetaObject::invokeMethod(VrMenu, "setRootMenu", Qt::AutoConnection, Q_ARG(QVariant, QVariant::fromValue(menu)));
}
QObject::disconnect(loader, SIGNAL(loaded()), this, SLOT(addButtonsToMenuScreen()));
}
void TabletProxy::removeButtonsFromHomeScreen() {
void TabletProxy::desktopWindowClosed() {
gotoHomeScreen();
}
void TabletProxy::addButtonsToToolbar() {
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
QObject* toolbarProxy = tabletScriptingInterface->getSystemToolbarProxy();
Qt::ConnectionType connectionType = Qt::AutoConnection;
if (QThread::currentThread() != toolbarProxy->thread()) {
connectionType = Qt::BlockingQueuedConnection;
}
for (auto& buttonProxy : _tabletButtonProxies) {
buttonProxy->setQmlButton(nullptr);
// copy properties from tablet button proxy to toolbar button proxy.
QObject* toolbarButtonProxy = nullptr;
bool hasResult = QMetaObject::invokeMethod(toolbarProxy, "addButton", connectionType, Q_RETURN_ARG(QObject*, toolbarButtonProxy), Q_ARG(QVariant, buttonProxy->getProperties()));
if (hasResult) {
buttonProxy->setToolbarButtonProxy(toolbarButtonProxy);
} else {
qCWarning(scriptengine) << "ToolbarProxy addButton has no result";
}
}
// make the toolbar visible
QMetaObject::invokeMethod(toolbarProxy, "writeProperty", Qt::AutoConnection, Q_ARG(QString, "visible"), Q_ARG(QVariant, QVariant(true)));
}
void TabletProxy::removeButtonsFromToolbar() {
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
QObject* toolbarProxy = tabletScriptingInterface->getSystemToolbarProxy();
for (auto& buttonProxy : _tabletButtonProxies) {
// remove button from toolbarProxy
QMetaObject::invokeMethod(toolbarProxy, "removeButton", Qt::AutoConnection, Q_ARG(QVariant, buttonProxy->getUuid().toString()));
buttonProxy->setToolbarButtonProxy(nullptr);
}
}
@ -430,12 +585,14 @@ QQuickItem* TabletProxy::getQmlMenu() const {
//
const QString UUID_KEY = "uuid";
const QString OBJECT_NAME_KEY = "objectName";
const QString STABLE_ORDER_KEY = "stableOrder";
static int s_stableOrder = 1;
TabletButtonProxy::TabletButtonProxy(const QVariantMap& properties) : _uuid(QUuid::createUuid()), _stableOrder(++s_stableOrder), _properties(properties) {
// this is used to uniquely identify this button.
_properties[UUID_KEY] = _uuid;
_properties[OBJECT_NAME_KEY] = _uuid.toString();
_properties[STABLE_ORDER_KEY] = _stableOrder;
}
@ -444,6 +601,14 @@ void TabletButtonProxy::setQmlButton(QQuickItem* qmlButton) {
_qmlButton = qmlButton;
}
void TabletButtonProxy::setToolbarButtonProxy(QObject* toolbarButtonProxy) {
std::lock_guard<std::mutex> guard(_mutex);
_toolbarButtonProxy = toolbarButtonProxy;
if (_toolbarButtonProxy) {
QObject::connect(_toolbarButtonProxy, SIGNAL(clicked()), this, SLOT(clickedSlot()));
}
}
QVariantMap TabletButtonProxy::getProperties() const {
std::lock_guard<std::mutex> guard(_mutex);
return _properties;
@ -451,6 +616,7 @@ QVariantMap TabletButtonProxy::getProperties() const {
void TabletButtonProxy::editProperties(QVariantMap properties) {
std::lock_guard<std::mutex> guard(_mutex);
QVariantMap::const_iterator iter = properties.constBegin();
while (iter != properties.constEnd()) {
_properties[iter.key()] = iter.value();
@ -459,6 +625,10 @@ void TabletButtonProxy::editProperties(QVariantMap properties) {
}
++iter;
}
if (_toolbarButtonProxy) {
QMetaObject::invokeMethod(_toolbarButtonProxy, "editProperties", Qt::AutoConnection, Q_ARG(QVariantMap, properties));
}
}
#include "TabletScriptingInterface.moc"

View file

@ -26,6 +26,7 @@
class TabletProxy;
class TabletButtonProxy;
class QmlWindowClass;
/**jsdoc
* @namespace Tablet
@ -35,6 +36,9 @@ class TabletScriptingInterface : public QObject, public Dependency {
public:
TabletScriptingInterface();
void setToolbarScriptingInterface(QObject* toolbarScriptingInterface) { _toolbarScriptingInterface = toolbarScriptingInterface; }
QObject* getSystemToolbarProxy();
/**jsdoc
* Creates or retruns a new TabletProxy and returns it.
* @function Tablet.getTablet
@ -43,6 +47,8 @@ public:
*/
Q_INVOKABLE QObject* getTablet(const QString& tabletId);
void setToolbarMode(bool toolbarMode);
void setQmlTabletRoot(QString tabletId, QQuickItem* qmlTabletRoot, QObject* qmlOffscreenSurface);
void processEvent(const QKeyEvent* event);
@ -58,15 +64,20 @@ private:
protected:
std::mutex _mutex;
std::map<QString, QSharedPointer<TabletProxy>> _tabletProxies;
QObject* _toolbarScriptingInterface { nullptr };
bool _toolbarMode { false };
};
/**jsdoc
* @class TabletProxy
* @property name {string} READ_ONLY: name of this tablet
* @property toolbarMode {bool} - used to transition this tablet into and out of toolbar mode.
* When tablet is in toolbar mode, all its buttons will appear in a floating toolbar.
*/
class TabletProxy : public QObject {
Q_OBJECT
Q_PROPERTY(QString name READ getName)
Q_PROPERTY(bool toolbarMode READ getToolbarMode WRITE setToolbarMode)
public:
TabletProxy(QString name);
@ -74,6 +85,11 @@ public:
Q_INVOKABLE void gotoMenuScreen(const QString& submenu = "");
QString getName() const { return _name; }
bool getToolbarMode() const { return _toolbarMode; }
void setToolbarMode(bool toolbarMode);
/**jsdoc
* transition to the home screen
* @function TabletProxy#gotoHomeScreen
@ -120,8 +136,6 @@ public:
*/
Q_INVOKABLE void updateAudioBar(const double micLevel);
QString getName() const { return _name; }
/**jsdoc
* Used to send an event to the html/js embedded in the tablet
* @function TabletProxy#emitScriptEvent
@ -162,24 +176,28 @@ signals:
void fromQml(QVariant msg);
/**jsdoc
* Signales when this tablet screen changes.
* Signaled when this tablet screen changes.
* @function TabletProxy#screenChanged
* @param type {string} - "Home", "Web", "Menu", "QML", "Closed"
* @param url {string} - only valid for Web and QML.
*/
void screenChanged(QVariant type, QVariant url);
private slots:
protected slots:
void addButtonsToHomeScreen();
void addButtonsToMenuScreen();
void desktopWindowClosed();
protected:
void removeButtonsFromHomeScreen();
void addButtonsToToolbar();
void removeButtonsFromToolbar();
QString _name;
std::mutex _mutex;
std::vector<QSharedPointer<TabletButtonProxy>> _tabletButtonProxies;
QQuickItem* _qmlTabletRoot { nullptr };
QObject* _qmlOffscreenSurface { nullptr };
QmlWindowClass* _desktopWindow { nullptr };
bool _toolbarMode { false };
enum class State { Uninitialized, Home, Web, Menu, QML };
State _state { State::Uninitialized };
@ -196,6 +214,7 @@ public:
TabletButtonProxy(const QVariantMap& properties);
void setQmlButton(QQuickItem* qmlButton);
void setToolbarButtonProxy(QObject* toolbarButtonProxy);
QUuid getUuid() const { return _uuid; }
@ -229,6 +248,7 @@ protected:
int _stableOrder;
mutable std::mutex _mutex;
QQuickItem* _qmlButton { nullptr };
QObject* _toolbarButtonProxy { nullptr };
QVariantMap _properties;
};

View file

@ -20,17 +20,22 @@ const QString InfoView::NAME{ "InfoView" };
Setting::Handle<QString> infoVersion("info-version", QString());
InfoView::InfoView(QQuickItem* parent) : QQuickItem(parent) {
static bool registered{ false };
InfoView::InfoView(QQuickItem* parent) : QQuickItem(parent) {
registerType();
}
void InfoView::registerType() {
qmlRegisterType<InfoView>("Hifi", 1, 0, NAME.toLocal8Bit().constData());
}
void InfoView::registerType() {
if (!registered) {
qmlRegisterType<InfoView>("Hifi", 1, 0, NAME.toLocal8Bit().constData());
registered = true;
}
}
QString fetchVersion(const QUrl& url) {
QXmlQuery query;
query.bindVariable("file", QVariant(url));
query.bindVariable("file", QVariant(url));
query.setQuery("string((doc($file)//input[@id='version'])[1]/@value)");
QString r;
query.evaluateTo(&r);
@ -38,14 +43,10 @@ QString fetchVersion(const QUrl& url) {
}
void InfoView::show(const QString& path, bool firstOrChangedOnly, QString urlQuery) {
static bool registered{ false };
if (!registered) {
registerType();
registered = true;
}
registerType();
QUrl url;
if (QDir(path).isRelative()) {
url = QUrl::fromLocalFile(PathUtils::resourcesPath() + path);
url = QUrl::fromLocalFile(PathUtils::resourcesPath() + path);
} else {
url = QUrl::fromLocalFile(path);
}
@ -56,7 +57,7 @@ void InfoView::show(const QString& path, bool firstOrChangedOnly, QString urlQue
const QString version = fetchVersion(url);
// If we have version information stored
if (lastVersion != QString::null) {
// Check to see the document version. If it's valid and matches
// Check to see the document version. If it's valid and matches
// the stored version, we're done, so exit
if (version == QString::null || version == lastVersion) {
return;
@ -87,4 +88,3 @@ void InfoView::setUrl(const QUrl& url) {
emit urlChanged();
}
}

View file

@ -31,6 +31,9 @@ public:
QmlWindowClass();
~QmlWindowClass();
virtual void initQml(QVariantMap properties);
QQuickItem* asQuickItem() const;
public slots:
bool isVisible() const;
void setVisible(bool visible);
@ -81,9 +84,6 @@ protected:
virtual QString qmlSource() const { return "QmlWindow.qml"; }
virtual void initQml(QVariantMap properties);
QQuickItem* asQuickItem() const;
// FIXME needs to be initialized in the ctor once we have support
// for tool window panes in QML
bool _toolWindow { false };

View file

@ -9,49 +9,30 @@
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */
(function() { // BEGIN LOCAL_SCOPE
var button;
var TOOLBAR_BUTTON_NAME = "MUTE";
var TABLET_BUTTON_NAME = "AUDIO";
var toolBar = null;
var tablet = null;
var isHUDUIEnabled = Settings.getValue("HUDUIEnabled");
var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png";
function onMuteToggled() {
if (isHUDUIEnabled) {
button.editProperties({ isActive: AudioDevice.getMuted() });
}
button.editProperties({ isActive: AudioDevice.getMuted() });
}
function onClicked(){
if (isHUDUIEnabled) {
var menuItem = "Mute Microphone";
Menu.setIsOptionChecked(menuItem, !Menu.isOptionChecked(menuItem));
} else {
var entity = HMD.tabletID;
Entities.editEntity(entity, { textures: JSON.stringify({ "tex.close": HOME_BUTTON_TEXTURE }) });
tablet.gotoMenuScreen("Audio");
}
var entity = HMD.tabletID;
Entities.editEntity(entity, { textures: JSON.stringify({ "tex.close": HOME_BUTTON_TEXTURE }) });
tablet.gotoMenuScreen("Audio");
}
if (Settings.getValue("HUDUIEnabled")) {
toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system");
button = toolBar.addButton({
objectName: TOOLBAR_BUTTON_NAME,
imageURL: Script.resolvePath("assets/images/tools/mic.svg"),
visible: true,
alpha: 0.9
});
} else {
tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
button = tablet.addButton({
icon: "icons/tablet-icons/mic-i.svg",
text: TABLET_BUTTON_NAME,
sortOrder: 1
});
}
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
var button = tablet.addButton({
icon: "icons/tablet-icons/mic-unmute-i.svg",
activeIcon: "icons/tablet-icons/mic-mute-a.svg",
text: TABLET_BUTTON_NAME,
sortOrder: 1
});
onMuteToggled();
button.clicked.connect(onClicked);
@ -60,12 +41,7 @@ AudioDevice.muteToggled.connect(onMuteToggled);
Script.scriptEnding.connect(function () {
button.clicked.disconnect(onClicked);
AudioDevice.muteToggled.disconnect(onMuteToggled);
if (tablet) {
tablet.removeButton(button);
}
if (toolBar) {
toolBar.removeButton(TOOLBAR_BUTTON_NAME);
}
tablet.removeButton(button);
});
}()); // END LOCAL_SCOPE

View file

@ -10,11 +10,9 @@
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
/* global Toolbars, Script, Users, Overlays, AvatarList, Controller, Camera, getControllerWorldLocation */
/* global Script, Users, Overlays, AvatarList, Controller, Camera, getControllerWorldLocation */
(function () { // BEGIN LOCAL_SCOPE
var button;
// Used for animating and disappearing the bubble
var bubbleOverlayTimestamp;
@ -23,7 +21,7 @@
// Used for flashing the HUD button upon activation
var bubbleButtonTimestamp;
// Affects bubble height
const BUBBLE_HEIGHT_SCALE = 0.15;
var BUBBLE_HEIGHT_SCALE = 0.15;
// The bubble model itself
var bubbleOverlay = Overlays.addOverlay("model", {
url: Script.resolvePath("assets/models/Bubble-v14.fbx"), // If you'd like to change the model, modify this line (and the dimensions below)
@ -39,16 +37,8 @@
// Is the update() function connected?
var updateConnected = false;
const BUBBLE_VISIBLE_DURATION_MS = 3000;
const BUBBLE_RAISE_ANIMATION_DURATION_MS = 750;
const BUBBLE_HUD_ICON_FLASH_INTERVAL_MS = 500;
var ASSETS_PATH = Script.resolvePath("assets");
var TOOLS_PATH = Script.resolvePath("assets/images/tools/");
function buttonImageURL() {
return TOOLS_PATH + 'bubble.svg';
}
var BUBBLE_VISIBLE_DURATION_MS = 3000;
var BUBBLE_RAISE_ANIMATION_DURATION_MS = 750;
// Hides the bubble model overlay and resets the button flash state
function hideOverlays() {
@ -94,7 +84,7 @@
}
// The bubble script's update function
update = function () {
function update() {
var timestamp = Date.now();
var delay = (timestamp - bubbleOverlayTimestamp);
var overlayAlpha = 1.0 - (delay / BUBBLE_VISIBLE_DURATION_MS);
@ -146,7 +136,7 @@
var bubbleActive = Users.getIgnoreRadiusEnabled();
writeButtonProperties(bubbleActive);
}
};
}
// When the space bubble is toggled...
function onBubbleToggled() {
@ -165,38 +155,26 @@
// Setup the bubble button
var buttonName = "BUBBLE";
if (Settings.getValue("HUDUIEnabled")) {
var toolbar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system");
button = toolbar.addButton({
objectName: 'bubble',
imageURL: buttonImageURL(),
visible: true,
alpha: 0.9
});
} else {
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
button = tablet.addButton({
icon: "icons/tablet-icons/bubble-i.svg",
activeIcon: "icons/tablet-icons/bubble-a.svg",
text: buttonName,
sortOrder: 4
});
}
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
button = tablet.addButton({
icon: "icons/tablet-icons/bubble-i.svg",
activeIcon: "icons/tablet-icons/bubble-a.svg",
text: buttonName,
sortOrder: 4
});
onBubbleToggled();
button.clicked.connect(Users.toggleIgnoreRadius);
Users.ignoreRadiusEnabledChanged.connect(onBubbleToggled);
Users.enteredIgnoreRadius.connect(enteredIgnoreRadius);
// Cleanup the toolbar button and overlays when script is stopped
// Cleanup the tablet button and overlays when script is stopped
Script.scriptEnding.connect(function () {
button.clicked.disconnect(Users.toggleIgnoreRadius);
if (tablet) {
tablet.removeButton(button);
}
if (toolbar) {
toolbar.removeButton('bubble');
}
Users.ignoreRadiusEnabledChanged.disconnect(onBubbleToggled);
Users.enteredIgnoreRadius.disconnect(enteredIgnoreRadius);
Overlays.deleteOverlay(bubbleOverlay);

View file

@ -480,6 +480,10 @@ var LASER_SEARCH_COLOR_XYZW = {x: 10 / 255, y: 10 / 255, z: 255 / 255, w: LASER_
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;
var HIFI_POINTER_DISABLE_MESSAGE_CHANNEL = "Hifi-Pointer-Disable";
var isPointerEnabled = true;
function clearSystemLaser() {
if (!systemLaserOn) {
return;
@ -542,9 +546,8 @@ function update() {
return off();
}
// If there's a HUD element at the (newly moved) reticle, just make it visible and bail.
if (isPointingAtOverlay(hudPoint2d)) {
if (isPointingAtOverlay(hudPoint2d) && isPointerEnabled) {
if (HMD.active) {
Reticle.depth = hudReticleDistance();
@ -579,9 +582,25 @@ function checkSettings() {
}
checkSettings();
// Enable/disable pointer.
function handleMessages(channel, message, sender) {
if (sender === MyAvatar.sessionUUID && channel === HIFI_POINTER_DISABLE_MESSAGE_CHANNEL) {
var data = JSON.parse(message);
if (data.pointerEnabled !== undefined) {
print("pointerEnabled: " + data.pointerEnabled);
isPointerEnabled = data.pointerEnabled;
}
}
}
Messages.subscribe(HIFI_POINTER_DISABLE_MESSAGE_CHANNEL);
Messages.messageReceived.connect(handleMessages);
var settingsChecker = Script.setInterval(checkSettings, SETTINGS_CHANGE_RECHECK_INTERVAL);
Script.update.connect(update);
Script.scriptEnding.connect(function () {
Messages.unsubscribe(HIFI_POINTER_DISABLE_MESSAGE_CHANNEL);
Messages.messageReceived.disconnect(handleMessages);
Script.clearInterval(settingsChecker);
Script.update.disconnect(update);
OffscreenFlags.navigationFocusDisabled = false;

View file

@ -25,6 +25,11 @@ var OVERLAY_RAMP_RATE = 8.0;
var animStateHandlerID;
var isPointingIndex = false;
var HIFI_POINT_INDEX_MESSAGE_CHANNEL = "Hifi-Point-Index";
var indexfingerJointNames = ["LeftHandIndex1", "LeftHandIndex2", "LeftHandIndex3", "RightHandIndex1", "RightHandIndex2", "RightHandIndex3"];
function clamp(val, min, max) {
return Math.min(Math.max(val, min), max);
}
@ -43,6 +48,8 @@ function init() {
animStateHandler,
["leftHandOverlayAlpha", "rightHandOverlayAlpha", "leftHandGraspAlpha", "rightHandGraspAlpha"]
);
Messages.subscribe(HIFI_POINT_INDEX_MESSAGE_CHANNEL);
Messages.messageReceived.connect(handleMessages);
}
function animStateHandler(props) {
@ -76,11 +83,37 @@ function update(dt) {
} else {
rightHandOverlayAlpha = clamp(rightHandOverlayAlpha - OVERLAY_RAMP_RATE * dt, 0, 1);
}
// Point index finger.
if (isPointingIndex) {
var zeroRotation = { x: 0, y: 0, z: 0, w: 1 };
for (var i = 0; i < indexfingerJointNames.length; i++) {
MyAvatar.setJointRotation(indexfingerJointNames[i], zeroRotation);
}
}
}
function handleMessages(channel, message, sender) {
if (sender === MyAvatar.sessionUUID && channel === HIFI_POINT_INDEX_MESSAGE_CHANNEL) {
var data = JSON.parse(message);
if (data.pointIndex !== undefined) {
print("pointIndex: " + data.pointIndex);
isPointingIndex = data.pointIndex;
if (!isPointingIndex) {
for (var i = 0; i < indexfingerJointNames.length; i++) {
MyAvatar.clearJointData(indexfingerJointNames[i]);
}
}
}
}
}
function shutdown() {
Script.update.disconnect(update);
MyAvatar.removeAnimationStateHandler(animStateHandlerID);
Messages.unsubscribe(HIFI_POINT_INDEX_MESSAGE_CHANNEL);
Messages.messageReceived.disconnect(handleMessages);
}
Script.scriptEnding.connect(shutdown);

View file

@ -0,0 +1,433 @@
//
// fingerPaint.js
//
// Created by David Rowe on 15 Feb 2017
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function () {
var tablet,
button,
BUTTON_NAME = "PAINT",
isFingerPainting = false,
leftHand = null,
rightHand = null,
leftBrush = null,
rightBrush = null,
CONTROLLER_MAPPING_NAME = "com.highfidelity.fingerPaint",
isTabletDisplayed = false,
HIFI_POINT_INDEX_MESSAGE_CHANNEL = "Hifi-Point-Index",
HIFI_GRAB_DISABLE_MESSAGE_CHANNEL = "Hifi-Grab-Disable",
HIFI_POINTER_DISABLE_MESSAGE_CHANNEL = "Hifi-Pointer-Disable";
function paintBrush(name) {
// Paints in 3D.
var brushName = name,
STROKE_COLOR = { red: 250, green: 0, blue: 0 },
ERASE_SEARCH_RADIUS = 0.1, // m
isDrawingLine = false,
entityID,
basePosition,
strokePoints,
strokeNormals,
strokeWidths,
timeOfLastPoint,
MIN_STROKE_LENGTH = 0.005, // m
MIN_STROKE_INTERVAL = 66, // ms
MAX_POINTS_PER_LINE = 70; // Hard-coded limit in PolyLineEntityItem.h.
function strokeNormal() {
return Vec3.multiplyQbyV(Camera.getOrientation(), Vec3.UNIT_NEG_Z);
}
function startLine(position, width) {
// Start drawing a polyline.
if (isDrawingLine) {
print("ERROR: startLine() called when already drawing line");
// Nevertheless, continue on and start a new line.
}
basePosition = position;
strokePoints = [Vec3.ZERO];
strokeNormals = [strokeNormal()];
strokeWidths = [width];
timeOfLastPoint = Date.now();
entityID = Entities.addEntity({
type: "PolyLine",
name: "fingerPainting",
color: STROKE_COLOR,
position: position,
linePoints: strokePoints,
normals: strokeNormals,
strokeWidths: strokeWidths,
dimensions: { x: 10, y: 10, z: 10 }
});
isDrawingLine = true;
}
function drawLine(position, width) {
// Add a stroke to the polyline if stroke is a sufficient length.
var localPosition,
distanceToPrevious,
MAX_DISTANCE_TO_PREVIOUS = 1.0;
if (!isDrawingLine) {
print("ERROR: drawLine() called when not drawing line");
return;
}
localPosition = Vec3.subtract(position, basePosition);
distanceToPrevious = Vec3.distance(localPosition, strokePoints[strokePoints.length - 1]);
if (distanceToPrevious > MAX_DISTANCE_TO_PREVIOUS) {
// Ignore occasional spurious finger tip positions.
return;
}
if (distanceToPrevious >= MIN_STROKE_LENGTH
&& (Date.now() - timeOfLastPoint) >= MIN_STROKE_INTERVAL
&& strokePoints.length < MAX_POINTS_PER_LINE) {
strokePoints.push(localPosition);
strokeNormals.push(strokeNormal());
strokeWidths.push(width);
timeOfLastPoint = Date.now();
Entities.editEntity(entityID, {
linePoints: strokePoints,
normals: strokeNormals,
strokeWidths: strokeWidths
});
}
}
function finishLine(position, width) {
// Finish drawing polyline; delete if it has only 1 point.
if (!isDrawingLine) {
print("ERROR: finishLine() called when not drawing line");
return;
}
if (strokePoints.length === 1) {
// Delete "empty" line.
Entities.deleteEntity(entityID);
}
isDrawingLine = false;
}
function cancelLine() {
// Cancel any line being drawn.
if (isDrawingLine) {
Entities.deleteEntity(entityID);
isDrawingLine = false;
}
}
function eraseClosestLine(position) {
// Erase closest line that is within search radius of finger tip.
var entities,
entitiesLength,
properties,
i,
pointsLength,
j,
distance,
found = false,
foundID,
foundDistance = ERASE_SEARCH_RADIUS;
// Find entities with bounding box within search radius.
entities = Entities.findEntities(position, ERASE_SEARCH_RADIUS);
// Fine polyline entity with closest point within search radius.
for (i = 0, entitiesLength = entities.length; i < entitiesLength; i += 1) {
properties = Entities.getEntityProperties(entities[i], ["type", "position", "linePoints"]);
if (properties.type === "PolyLine") {
basePosition = properties.position;
for (j = 0, pointsLength = properties.linePoints.length; j < pointsLength; j += 1) {
distance = Vec3.distance(position, Vec3.sum(basePosition, properties.linePoints[j]));
if (distance <= foundDistance) {
found = true;
foundID = entities[i];
foundDistance = distance;
}
}
}
}
// Delete found entity.
if (found) {
Entities.deleteEntity(foundID);
}
}
function tearDown() {
cancelLine();
}
return {
startLine: startLine,
drawLine: drawLine,
finishLine: finishLine,
cancelLine: cancelLine,
eraseClosestLine: eraseClosestLine,
tearDown: tearDown
};
}
function handController(name) {
// Translates controller data into application events.
var handName = name,
triggerPressedCallback,
triggerPressingCallback,
triggerReleasedCallback,
gripPressedCallback,
rawTriggerValue = 0.0,
triggerValue = 0.0,
isTriggerPressed = false,
TRIGGER_SMOOTH_RATIO = 0.1,
TRIGGER_OFF = 0.05,
TRIGGER_ON = 0.1,
TRIGGER_START_WIDTH_RAMP = 0.15,
TRIGGER_FINISH_WIDTH_RAMP = 1.0,
TRIGGER_RAMP_WIDTH = TRIGGER_FINISH_WIDTH_RAMP - TRIGGER_START_WIDTH_RAMP,
MIN_LINE_WIDTH = 0.005,
MAX_LINE_WIDTH = 0.03,
RAMP_LINE_WIDTH = MAX_LINE_WIDTH - MIN_LINE_WIDTH,
rawGripValue = 0.0,
gripValue = 0.0,
isGripPressed = false,
GRIP_SMOOTH_RATIO = 0.1,
GRIP_OFF = 0.05,
GRIP_ON = 0.1;
function onTriggerPress(value) {
// Controller values are only updated when they change so store latest for use in update.
rawTriggerValue = value;
}
function updateTriggerPress(value) {
var wasTriggerPressed,
fingerTipPosition,
lineWidth;
triggerValue = triggerValue * TRIGGER_SMOOTH_RATIO + rawTriggerValue * (1.0 - TRIGGER_SMOOTH_RATIO);
wasTriggerPressed = isTriggerPressed;
if (isTriggerPressed) {
isTriggerPressed = triggerValue > TRIGGER_OFF;
} else {
isTriggerPressed = triggerValue > TRIGGER_ON;
}
if (wasTriggerPressed || isTriggerPressed) {
fingerTipPosition = MyAvatar.getJointPosition(handName === "left" ? "LeftHandIndex4" : "RightHandIndex4");
if (triggerValue < TRIGGER_START_WIDTH_RAMP) {
lineWidth = MIN_LINE_WIDTH;
} else {
lineWidth = MIN_LINE_WIDTH
+ (triggerValue - TRIGGER_START_WIDTH_RAMP) / TRIGGER_RAMP_WIDTH * RAMP_LINE_WIDTH;
}
if (!wasTriggerPressed && isTriggerPressed) {
triggerPressedCallback(fingerTipPosition, lineWidth);
} else if (wasTriggerPressed && isTriggerPressed) {
triggerPressingCallback(fingerTipPosition, lineWidth);
} else {
triggerReleasedCallback(fingerTipPosition, lineWidth);
}
}
}
function onGripPress(value) {
// Controller values are only updated when they change so store latest for use in update.
rawGripValue = value;
}
function updateGripPress() {
var fingerTipPosition;
gripValue = gripValue * GRIP_SMOOTH_RATIO + rawGripValue * (1.0 - GRIP_SMOOTH_RATIO);
if (isGripPressed) {
isGripPressed = gripValue > GRIP_OFF;
} else {
isGripPressed = gripValue > GRIP_ON;
if (isGripPressed) {
fingerTipPosition = MyAvatar.getJointPosition(handName === "left" ? "LeftHandIndex4" : "RightHandIndex4");
gripPressedCallback(fingerTipPosition);
}
}
}
function onUpdate() {
updateTriggerPress();
updateGripPress();
}
function setUp(onTriggerPressed, onTriggerPressing, onTriggerReleased, onGripPressed) {
triggerPressedCallback = onTriggerPressed;
triggerPressingCallback = onTriggerPressing;
triggerReleasedCallback = onTriggerReleased;
gripPressedCallback = onGripPressed;
}
function tearDown() {
// Nothing to do.
}
return {
onTriggerPress: onTriggerPress,
onGripPress: onGripPress,
onUpdate: onUpdate,
setUp: setUp,
tearDown: tearDown
};
}
function updateHandFunctions() {
// Update other scripts' hand functions.
var enabled = !isFingerPainting || isTabletDisplayed;
Messages.sendMessage(HIFI_GRAB_DISABLE_MESSAGE_CHANNEL, JSON.stringify({
holdEnabled: enabled,
nearGrabEnabled: enabled,
farGrabEnabled: enabled
}), true);
Messages.sendMessage(HIFI_POINTER_DISABLE_MESSAGE_CHANNEL, JSON.stringify({
pointerEnabled: enabled
}), true);
Messages.sendMessage(HIFI_POINT_INDEX_MESSAGE_CHANNEL, JSON.stringify({
pointIndex: !enabled
}), true);
}
function enableProcessing() {
// Connect controller API to handController objects.
leftHand = handController("left");
rightHand = handController("right");
var controllerMapping = Controller.newMapping(CONTROLLER_MAPPING_NAME);
controllerMapping.from(Controller.Standard.LT).to(leftHand.onTriggerPress);
controllerMapping.from(Controller.Standard.LeftGrip).to(leftHand.onGripPress);
controllerMapping.from(Controller.Standard.RT).to(rightHand.onTriggerPress);
controllerMapping.from(Controller.Standard.RightGrip).to(rightHand.onGripPress);
Controller.enableMapping(CONTROLLER_MAPPING_NAME);
// Connect handController outputs to paintBrush objects.
leftBrush = paintBrush("left");
leftHand.setUp(leftBrush.startLine, leftBrush.drawLine, leftBrush.finishLine, leftBrush.eraseClosestLine);
rightBrush = paintBrush("right");
rightHand.setUp(rightBrush.startLine, rightBrush.drawLine, rightBrush.finishLine, rightBrush.eraseClosestLine);
// Messages channels for enabling/disabling other scripts' functions.
Messages.subscribe(HIFI_POINT_INDEX_MESSAGE_CHANNEL);
Messages.subscribe(HIFI_GRAB_DISABLE_MESSAGE_CHANNEL);
Messages.subscribe(HIFI_POINTER_DISABLE_MESSAGE_CHANNEL);
// Update hand controls.
Script.update.connect(leftHand.onUpdate);
Script.update.connect(rightHand.onUpdate);
}
function disableProcessing() {
Script.update.disconnect(leftHand.onUpdate);
Script.update.disconnect(rightHand.onUpdate);
Controller.disableMapping(CONTROLLER_MAPPING_NAME);
leftBrush.tearDown();
leftBrush = null;
leftHand.tearDown();
leftHand = null;
rightBrush.tearDown();
rightBrush = null;
rightHand.tearDown();
rightHand = null;
Messages.unsubscribe(HIFI_POINT_INDEX_MESSAGE_CHANNEL);
Messages.unsubscribe(HIFI_GRAB_DISABLE_MESSAGE_CHANNEL);
Messages.unsubscribe(HIFI_POINTER_DISABLE_MESSAGE_CHANNEL);
}
function onButtonClicked() {
var wasFingerPainting = isFingerPainting;
isFingerPainting = !isFingerPainting;
button.editProperties({ isActive: isFingerPainting });
print("Finger painting: " + isFingerPainting ? "on" : "off");
if (wasFingerPainting) {
leftBrush.cancelLine();
rightBrush.cancelLine();
}
if (isFingerPainting) {
enableProcessing();
}
updateHandFunctions();
if (!isFingerPainting) {
disableProcessing();
}
}
function onTabletScreenChanged(type, url) {
var TABLET_SCREEN_CLOSED = "Closed";
isTabletDisplayed = type !== TABLET_SCREEN_CLOSED;
updateHandFunctions();
}
function setUp() {
tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
if (!tablet) {
return;
}
// Tablet button.
button = tablet.addButton({
icon: "icons/tablet-icons/finger-paint-i.svg",
activeIcon: "icons/tablet-icons/finger-paint-a.svg",
text: BUTTON_NAME,
isActive: isFingerPainting
});
button.clicked.connect(onButtonClicked);
// Track whether tablet is displayed or not.
tablet.screenChanged.connect(onTabletScreenChanged);
}
function tearDown() {
if (!tablet) {
return;
}
if (isFingerPainting) {
isFingerPainting = false;
updateHandFunctions();
disableProcessing();
}
tablet.screenChanged.disconnect(onTabletScreenChanged);
button.clicked.disconnect(onButtonClicked);
tablet.removeButton(button);
}
setUp();
Script.scriptEnding.connect(tearDown);
}());

View file

@ -10,48 +10,21 @@
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
/* globals Tablet, Toolbars, Script, HMD, Controller, Menu */
/* globals Tablet, Script, HMD, Controller, Menu */
(function() { // BEGIN LOCAL_SCOPE
var button;
var buttonName = "HELP";
var toolBar = null;
var tablet = null;
if (Settings.getValue("HUDUIEnabled")) {
toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system");
button = toolBar.addButton({
objectName: buttonName,
imageURL: Script.resolvePath("assets/images/tools/help.svg"),
visible: true,
alpha: 0.9
});
} else {
tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
button = tablet.addButton({
icon: "icons/tablet-icons/help-i.svg",
activeIcon: "icons/tablet-icons/help-a.svg",
text: buttonName,
sortOrder: 6
});
}
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
var button = tablet.addButton({
icon: "icons/tablet-icons/help-i.svg",
activeIcon: "icons/tablet-icons/help-a.svg",
text: buttonName,
sortOrder: 6
});
var enabled = false;
function onClicked() {
// Similar logic to Application::showHelp()
var defaultTab = "kbm";
var handControllerName = "vive";
if (HMD.active) {
if ("Vive" in Controller.Hardware) {
defaultTab = "handControllers";
handControllerName = "vive";
} else if ("OculusTouch" in Controller.Hardware) {
defaultTab = "handControllers";
handControllerName = "oculus";
}
} else if ("SDL2" in Controller.Hardware) {
defaultTab = "gamepad";
}
if (enabled) {
Menu.closeInfoView('InfoView_html/help.html');
enabled = !enabled;
@ -80,9 +53,6 @@
if (tablet) {
tablet.removeButton(button);
}
if (toolBar) {
toolBar.removeButton(buttonName);
}
});
}()); // END LOCAL_SCOPE

View file

@ -10,7 +10,8 @@
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
/*globals HMD, Toolbars, Script, Menu, Tablet, Camera */
/* globals HMD, Script, Menu, Tablet, Camera */
/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */
(function() { // BEGIN LOCAL_SCOPE
@ -37,20 +38,13 @@ function updateControllerDisplay() {
}
var button;
var toolBar = null;
var tablet = null;
if (Settings.getValue("HUDUIEnabled")) {
toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system");
} else {
tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
}
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
// Independent and Entity mode make people sick. Third Person and Mirror have traps that we need to work through.
// Disable them in hmd.
var desktopOnlyViews = ['Mirror', 'Independent Mode', 'Entity Mode'];
function onHmdChanged(isHmd) {
//TODO change button icon when the hmd changes
if (isHmd) {
button.editProperties({
icon: "icons/tablet-icons/switch-desk-i.svg",
@ -67,25 +61,18 @@ function onHmdChanged(isHmd) {
});
updateControllerDisplay();
}
function onClicked(){
function onClicked() {
var isDesktop = Menu.isOptionChecked(desktopMenuItemName);
Menu.setIsOptionChecked(isDesktop ? headset : desktopMenuItemName, true);
}
if (headset) {
if (Settings.getValue("HUDUIEnabled")) {
button = toolBar.addButton({
objectName: "hmdToggle",
imageURL: Script.resolvePath("assets/images/tools/switch.svg"),
visible: true,
alpha: 0.9
});
} else {
button = tablet.addButton({
icon: HMD.active ? "icons/tablet-icons/switch-desk-i.svg" : "icons/tablet-icons/switch-vr-i.svg",
text: HMD.active ? "DESKTOP" : "VR",
sortOrder: 2
});
}
button = tablet.addButton({
icon: HMD.active ? "icons/tablet-icons/switch-desk-i.svg" : "icons/tablet-icons/switch-vr-i.svg",
text: HMD.active ? "DESKTOP" : "VR",
sortOrder: 2
});
onHmdChanged(HMD.active);
button.clicked.connect(onClicked);
@ -97,9 +84,6 @@ if (headset) {
if (tablet) {
tablet.removeButton(button);
}
if (toolBar) {
toolBar.removeButton("hmdToggle");
}
HMD.displayModeChanged.disconnect(onHmdChanged);
Camera.modeUpdated.disconnect(updateControllerDisplay);
});

View file

@ -8,7 +8,7 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
/* global Tablet, Script, HMD, Toolbars, UserActivityLogger, Entities */
/* global Tablet, Script, HMD, UserActivityLogger, Entities */
/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */
(function() { // BEGIN LOCAL_SCOPE
@ -33,8 +33,6 @@ var QUERY_CAN_WRITE_ASSETS = "QUERY_CAN_WRITE_ASSETS";
var CAN_WRITE_ASSETS = "CAN_WRITE_ASSETS";
var WARN_USER_NO_PERMISSIONS = "WARN_USER_NO_PERMISSIONS";
var marketplaceWindow = null;
var CLARA_DOWNLOAD_TITLE = "Preparing Download";
var messageBox = null;
var isDownloadBeingCancelled = false;
@ -57,52 +55,47 @@ Window.messageBoxClosed.connect(onMessageBoxClosed);
function showMarketplace() {
UserActivityLogger.openedMarketplace();
if (tablet) {
tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL);
tablet.webEventReceived.connect(function (message) {
if (message === GOTO_DIRECTORY) {
tablet.gotoWebScreen(MARKETPLACES_URL);
}
tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL);
tablet.webEventReceived.connect(function (message) {
if (message === QUERY_CAN_WRITE_ASSETS) {
tablet.emitScriptEvent(CAN_WRITE_ASSETS + " " + Entities.canWriteAssets());
}
if (message === GOTO_DIRECTORY) {
tablet.gotoWebScreen(MARKETPLACES_URL, MARKETPLACES_INJECT_SCRIPT_URL);
}
if (message === WARN_USER_NO_PERMISSIONS) {
Window.alert(NO_PERMISSIONS_ERROR_MESSAGE);
}
if (message === QUERY_CAN_WRITE_ASSETS) {
tablet.emitScriptEvent(CAN_WRITE_ASSETS + " " + Entities.canWriteAssets());
}
if (message.slice(0, CLARA_IO_STATUS.length) === CLARA_IO_STATUS) {
if (isDownloadBeingCancelled) {
return;
}
if (message === WARN_USER_NO_PERMISSIONS) {
Window.alert(NO_PERMISSIONS_ERROR_MESSAGE);
}
var text = message.slice(CLARA_IO_STATUS.length);
if (messageBox === null) {
messageBox = Window.openMessageBox(CLARA_DOWNLOAD_TITLE, text, CANCEL_BUTTON, NO_BUTTON);
} else {
Window.updateMessageBox(messageBox, CLARA_DOWNLOAD_TITLE, text, CANCEL_BUTTON, NO_BUTTON);
}
if (message.slice(0, CLARA_IO_STATUS.length) === CLARA_IO_STATUS) {
if (isDownloadBeingCancelled) {
return;
}
if (message.slice(0, CLARA_IO_DOWNLOAD.length) === CLARA_IO_DOWNLOAD) {
if (messageBox !== null) {
Window.closeMessageBox(messageBox);
messageBox = null;
}
return;
var text = message.slice(CLARA_IO_STATUS.length);
if (messageBox === null) {
messageBox = Window.openMessageBox(CLARA_DOWNLOAD_TITLE, text, CANCEL_BUTTON, NO_BUTTON);
} else {
Window.updateMessageBox(messageBox, CLARA_DOWNLOAD_TITLE, text, CANCEL_BUTTON, NO_BUTTON);
}
return;
}
if (message === CLARA_IO_CANCELLED_DOWNLOAD) {
isDownloadBeingCancelled = false;
if (message.slice(0, CLARA_IO_DOWNLOAD.length) === CLARA_IO_DOWNLOAD) {
if (messageBox !== null) {
Window.closeMessageBox(messageBox);
messageBox = null;
}
});
} else {
marketplaceWindow.setURL(MARKETPLACE_URL_INITIAL);
marketplaceWindow.setVisible(true);
marketplaceVisible = true;
}
return;
}
if (message === CLARA_IO_CANCELLED_DOWNLOAD) {
isDownloadBeingCancelled = false;
}
});
}
function toggleMarketplace() {
@ -111,33 +104,12 @@ function toggleMarketplace() {
showMarketplace();
}
var tablet = null;
var toolBar = null;
var marketplaceButton = null;
if (Settings.getValue("HUDUIEnabled")) {
marketplaceWindow = new OverlayWebWindow({
title: "Marketplace",
source: "about:blank",
width: 900,
height: 700,
visible: false
});
marketplaceWindow.setScriptURL(MARKETPLACES_INJECT_SCRIPT_URL);
toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system");
var toolIconUrl = Script.resolvePath("../assets/images/tools/");
marketplaceButton = toolBar.addButton({
imageURL: toolIconUrl + "market.svg",
objectName: "marketplace",
alpha: 0.9
});
} else {
tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
marketplaceButton = tablet.addButton({
icon: "icons/tablet-icons/market-i.svg",
text: "MARKET",
sortOrder: 9
});
}
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
var marketplaceButton = tablet.addButton({
icon: "icons/tablet-icons/market-i.svg",
text: "MARKET",
sortOrder: 9
});
function onCanWriteAssetsChanged() {
var message = CAN_WRITE_ASSETS + " " + Entities.canWriteAssets();
@ -152,9 +124,6 @@ marketplaceButton.clicked.connect(onClick);
Entities.canWriteAssetsChanged.connect(onCanWriteAssetsChanged);
Script.scriptEnding.connect(function () {
if (toolBar) {
toolBar.removeButton("marketplace");
}
if (tablet) {
tablet.removeButton(marketplaceButton);
}

View file

@ -1,6 +1,7 @@
"use strict";
/*jslint vars: true, plusplus: true, forin: true*/
/*globals Script, AvatarList, Users, Entities, MyAvatar, Camera, Overlays, OverlayWindow, Toolbars, Vec3, Quat, Controller, print, getControllerWorldLocation */
/* jslint vars: true, plusplus: true, forin: true*/
/* globals Tablet, Script, AvatarList, Users, Entities, MyAvatar, Camera, Overlays, Vec3, Quat, Controller, print, getControllerWorldLocation */
/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */
//
// pal.js
//
@ -13,21 +14,24 @@
(function() { // BEGIN LOCAL_SCOPE
// hardcoding these as it appears we cannot traverse the originalTextures in overlays??? Maybe I've missed
// hardcoding these as it appears we cannot traverse the originalTextures in overlays??? Maybe I've missed
// something, will revisit as this is sorta horrible.
const UNSELECTED_TEXTURES = {"idle-D": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-idle.png"),
"idle-E": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-idle.png")
var UNSELECTED_TEXTURES = {
"idle-D": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-idle.png"),
"idle-E": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-idle.png")
};
const SELECTED_TEXTURES = { "idle-D": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-selected.png"),
"idle-E": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-selected.png")
var SELECTED_TEXTURES = {
"idle-D": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-selected.png"),
"idle-E": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-selected.png")
};
const HOVER_TEXTURES = { "idle-D": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-hover.png"),
"idle-E": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-hover.png")
var HOVER_TEXTURES = {
"idle-D": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-hover.png"),
"idle-E": Script.resolvePath("./assets/models/Avatar-Overlay-v1.fbx/Avatar-Overlay-v1.fbm/avatar-overlay-hover.png")
};
const UNSELECTED_COLOR = { red: 0x1F, green: 0xC6, blue: 0xA6};
const SELECTED_COLOR = {red: 0xF3, green: 0x91, blue: 0x29};
const HOVER_COLOR = {red: 0xD0, green: 0xD0, blue: 0xD0}; // almost white for now
var UNSELECTED_COLOR = { red: 0x1F, green: 0xC6, blue: 0xA6};
var SELECTED_COLOR = {red: 0xF3, green: 0x91, blue: 0x29};
var HOVER_COLOR = {red: 0xD0, green: 0xD0, blue: 0xD0}; // almost white for now
var conserveResources = true;
@ -87,24 +91,24 @@ ExtendedOverlay.prototype.hover = function (hovering) {
} else {
lastHoveringId = 0;
}
}
}
this.editOverlay({color: color(this.selected, hovering, this.audioLevel)});
if (this.model) {
this.model.editOverlay({textures: textures(this.selected, hovering)});
}
if (hovering) {
// un-hover the last hovering overlay
if (lastHoveringId && lastHoveringId != this.key) {
if (lastHoveringId && lastHoveringId !== this.key) {
ExtendedOverlay.get(lastHoveringId).hover(false);
}
lastHoveringId = this.key;
}
}
};
ExtendedOverlay.prototype.select = function (selected) {
if (this.selected === selected) {
return;
}
UserActivityLogger.palAction(selected ? "avatar_selected" : "avatar_deselected", this.key);
this.editOverlay({color: color(selected, this.hovering, this.audioLevel)});
@ -193,17 +197,8 @@ HighlightedEntity.updateOverlays = function updateHighlightedEntities() {
});
};
//
// The qml window and communications.
//
var pal = new OverlayWindow({
title: 'People Action List',
source: 'hifi/Pal.qml',
width: 580,
height: 640,
visible: false
});
function fromQml(message) { // messages are {method, params}, like json-rpc. See also sendToQml.
var data;
switch (message.method) {
case 'selected':
selectedIds = message.params;
@ -250,7 +245,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See
}
break;
case 'displayNameUpdate':
if (MyAvatar.displayName != message.params) {
if (MyAvatar.displayName !== message.params) {
MyAvatar.displayName = message.params;
UserActivityLogger.palAction("display_name_change", "");
}
@ -261,11 +256,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See
}
function sendToQml(message) {
if (Settings.getValue("HUDUIEnabled")) {
pal.sendToQml(message);
} else {
tablet.sendToQml(message);
}
tablet.sendToQml(message);
}
//
@ -273,7 +264,7 @@ function sendToQml(message) {
//
function addAvatarNode(id) {
var selected = ExtendedOverlay.isSelected(id);
return new ExtendedOverlay(id, "sphere", {
return new ExtendedOverlay(id, "sphere", {
drawInFront: true,
solid: true,
alpha: 0.8,
@ -341,7 +332,7 @@ function updateOverlays() {
var target = avatar.position;
var distance = Vec3.distance(target, eye);
var offset = 0.2;
// base offset on 1/2 distance from hips to head if we can
var headIndex = avatar.getJointIndex("Head");
if (headIndex > 0) {
@ -350,7 +341,7 @@ function updateOverlays() {
// get diff between target and eye (a vector pointing to the eye from avatar position)
var diff = Vec3.subtract(target, eye);
// move a bit in front, towards the camera
target = Vec3.subtract(target, Vec3.multiply(Vec3.normalize(diff), offset));
@ -361,12 +352,12 @@ function updateOverlays() {
overlay.editOverlay({
color: color(ExtendedOverlay.isSelected(id), overlay.hovering, overlay.audioLevel),
position: target,
dimensions: 0.032 * distance
dimensions: 0.032 * distance
});
if (overlay.model) {
overlay.model.ping = pingPong;
overlay.model.editOverlay({
position: target,
position: target,
scale: 0.2 * distance, // constant apparent size
rotation: Camera.orientation
});
@ -385,7 +376,9 @@ function removeOverlays() {
selectedIds = [];
lastHoveringId = 0;
HighlightedEntity.clearOverlays();
ExtendedOverlay.some(function (overlay) { overlay.deleteOverlay(); });
ExtendedOverlay.some(function (overlay) {
overlay.deleteOverlay();
});
}
//
@ -415,12 +408,13 @@ function handleMouseMove(pickRay) { // given the pickRay, just do the hover logi
// handy global to keep track of which hand is the mouse (if any)
var currentHandPressed = 0;
const TRIGGER_CLICK_THRESHOLD = 0.85;
const TRIGGER_PRESS_THRESHOLD = 0.05;
var TRIGGER_CLICK_THRESHOLD = 0.85;
var TRIGGER_PRESS_THRESHOLD = 0.05;
function handleMouseMoveEvent(event) { // find out which overlay (if any) is over the mouse position
var pickRay;
if (HMD.active) {
if (currentHandPressed != 0) {
if (currentHandPressed !== 0) {
pickRay = controllerComputePickRay(currentHandPressed);
} else {
// nothing should hover, so
@ -433,18 +427,18 @@ function handleMouseMoveEvent(event) { // find out which overlay (if any) is ove
handleMouseMove(pickRay);
}
function handleTriggerPressed(hand, value) {
// The idea is if you press one trigger, it is the one
// The idea is if you press one trigger, it is the one
// we will consider the mouse. Even if the other is pressed,
// we ignore it until this one is no longer pressed.
isPressed = value > TRIGGER_PRESS_THRESHOLD;
if (currentHandPressed == 0) {
var isPressed = value > TRIGGER_PRESS_THRESHOLD;
if (currentHandPressed === 0) {
currentHandPressed = isPressed ? hand : 0;
return;
}
if (currentHandPressed == hand) {
if (currentHandPressed === hand) {
currentHandPressed = isPressed ? hand : 0;
return;
}
}
// otherwise, the other hand is still triggered
// so do nothing.
}
@ -470,7 +464,7 @@ function makeClickHandler(hand) {
function makePressHandler(hand) {
return function (value) {
handleTriggerPressed(hand, value);
}
};
}
triggerMapping.from(Controller.Standard.RTClick).peek().to(makeClickHandler(Controller.Standard.RightHand));
triggerMapping.from(Controller.Standard.LTClick).peek().to(makeClickHandler(Controller.Standard.LeftHand));
@ -482,17 +476,14 @@ triggerPressMapping.from(Controller.Standard.LT).peek().to(makePressHandler(Cont
var button;
var buttonName = "PEOPLE";
var tablet = null;
var toolBar = null;
if (Settings.getValue("HUDUIEnabled")) {
toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system");
button = toolBar.addButton({
objectName: buttonName,
imageURL: Script.resolvePath("assets/images/tools/people.svg"),
visible: true,
alpha: 0.9
});
pal.fromQml.connect(fromQml);
} else {
function onTabletScreenChanged(type, url) {
if (type !== "QML" || url !== "../Pal.qml") {
off();
}
}
function startup() {
tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
button = tablet.addButton({
text: buttonName,
@ -500,8 +491,19 @@ if (Settings.getValue("HUDUIEnabled")) {
sortOrder: 7
});
tablet.fromQml.connect(fromQml);
button.clicked.connect(onTabletButtonClicked);
tablet.screenChanged.connect(onTabletScreenChanged);
Users.usernameFromIDReply.connect(usernameFromIDReply);
Window.domainChanged.connect(clearLocalQMLDataAndClosePAL);
Window.domainConnectionRefused.connect(clearLocalQMLDataAndClosePAL);
Messages.subscribe(CHANNEL);
Messages.messageReceived.connect(receiveMessage);
Users.avatarDisconnected.connect(avatarDisconnected);
}
startup();
var isWired = false;
var audioTimer;
var AUDIO_LEVEL_UPDATE_INTERVAL_MS = 100; // 10hz for now (change this and change the AVERAGING_RATIO too)
@ -513,41 +515,26 @@ function off() {
Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent);
isWired = false;
}
if (audioTimer) { Script.clearInterval(audioTimer); }
if (audioTimer) {
Script.clearInterval(audioTimer);
}
triggerMapping.disable(); // It's ok if we disable twice.
triggerPressMapping.disable(); // see above
removeOverlays();
Users.requestsDomainListData = false;
}
function onClicked() {
if (Settings.getValue("HUDUIEnabled")) {
if (!pal.visible) {
Users.requestsDomainListData = true;
populateUserList();
pal.raise();
isWired = true;
Script.update.connect(updateOverlays);
Controller.mousePressEvent.connect(handleMouseEvent);
Controller.mouseMoveEvent.connect(handleMouseMoveEvent);
triggerMapping.enable();
triggerPressMapping.enable();
audioTimer = createAudioInterval(conserveResources ? AUDIO_LEVEL_CONSERVED_UPDATE_INTERVAL_MS : AUDIO_LEVEL_UPDATE_INTERVAL_MS);
} else {
off();
}
pal.setVisible(!pal.visible);
} else {
tablet.loadQMLSource("../Pal.qml");
Users.requestsDomainListData = true;
populateUserList();
isWired = true;
Script.update.connect(updateOverlays);
Controller.mousePressEvent.connect(handleMouseEvent);
Controller.mouseMoveEvent.connect(handleMouseMoveEvent);
triggerMapping.enable();
triggerPressMapping.enable();
audioTimer = createAudioInterval(conserveResources ? AUDIO_LEVEL_CONSERVED_UPDATE_INTERVAL_MS : AUDIO_LEVEL_UPDATE_INTERVAL_MS);
}
function onTabletButtonClicked() {
tablet.loadQMLSource("../Pal.qml");
Users.requestsDomainListData = true;
populateUserList();
isWired = true;
Script.update.connect(updateOverlays);
Controller.mousePressEvent.connect(handleMouseEvent);
Controller.mouseMoveEvent.connect(handleMouseMoveEvent);
triggerMapping.enable();
triggerPressMapping.enable();
audioTimer = createAudioInterval(conserveResources ? AUDIO_LEVEL_CONSERVED_UPDATE_INTERVAL_MS : AUDIO_LEVEL_UPDATE_INTERVAL_MS);
}
//
@ -562,17 +549,12 @@ function receiveMessage(channel, messageString, senderID) {
var message = JSON.parse(messageString);
switch (message.method) {
case 'select':
if (!pal.visible) {
onClicked();
}
sendToQml(message); // Accepts objects, not just strings.
break;
default:
print('Unrecognized PAL message', messageString);
}
}
Messages.subscribe(CHANNEL);
Messages.messageReceived.connect(receiveMessage);
var AVERAGING_RATIO = 0.05;
@ -630,57 +612,29 @@ function avatarDisconnected(nodeID) {
// remove from the pal list
sendToQml({method: 'avatarDisconnected', params: [nodeID]});
}
//
// Button state.
//
function onVisibleChanged() {
button.editProperties({isActive: pal.visible});
}
button.clicked.connect(onClicked);
pal.visibleChanged.connect(onVisibleChanged);
pal.closed.connect(off);
if (!Settings.getValue("HUDUIEnabled")) {
tablet.screenChanged.connect(function (type, url) {
if (type !== "QML" || url !== "../Pal.qml") {
off();
}
});
}
Users.usernameFromIDReply.connect(usernameFromIDReply);
Users.avatarDisconnected.connect(avatarDisconnected);
function clearLocalQMLDataAndClosePAL() {
sendToQml({ method: 'clearLocalQMLData' });
if (pal.visible) {
onClicked(); // Close the PAL
}
}
Window.domainChanged.connect(clearLocalQMLDataAndClosePAL);
Window.domainConnectionRefused.connect(clearLocalQMLDataAndClosePAL);
function shutdown() {
button.clicked.disconnect(onTabletButtonClicked);
tablet.removeButton(button);
tablet.screenChanged.disconnect(onTabletScreenChanged);
Users.usernameFromIDReply.disconnect(usernameFromIDReply);
Window.domainChanged.disconnect(clearLocalQMLDataAndClosePAL);
Window.domainConnectionRefused.disconnect(clearLocalQMLDataAndClosePAL);
Messages.subscribe(CHANNEL);
Messages.messageReceived.disconnect(receiveMessage);
Users.avatarDisconnected.disconnect(avatarDisconnected);
off();
}
//
// Cleanup.
//
Script.scriptEnding.connect(function () {
button.clicked.disconnect(onClicked);
if (tablet) {
tablet.removeButton(button);
}
if (toolBar) {
toolBar.removeButton(buttonName);
}
pal.visibleChanged.disconnect(onVisibleChanged);
pal.closed.disconnect(off);
Users.usernameFromIDReply.disconnect(usernameFromIDReply);
Window.domainChanged.disconnect(clearLocalQMLDataAndClosePAL);
Window.domainConnectionRefused.disconnect(clearLocalQMLDataAndClosePAL);
Messages.unsubscribe(CHANNEL);
Messages.messageReceived.disconnect(receiveMessage);
Users.avatarDisconnected.disconnect(avatarDisconnected);
off();
});
Script.scriptEnding.connect(shutdown);
}()); // END LOCAL_SCOPE

View file

@ -7,7 +7,8 @@
// Distributed under the Apache License, Version 2.0
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
/* globals Tablet, Toolbars, Script, HMD, Settings, DialogsManager, Menu, Reticle, OverlayWebWindow, Desktop, Account, MyAvatar */
/* globals Tablet, Script, HMD, Settings, DialogsManager, Menu, Reticle, OverlayWebWindow, Desktop, Account, MyAvatar */
/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */
(function() { // BEGIN LOCAL_SCOPE
@ -17,29 +18,15 @@ var resetOverlays;
var reticleVisible;
var clearOverlayWhenMoving;
var button;
var buttonName = "SNAP";
var tablet = null;
var toolBar = null;
var buttonConnected = false;
if (Settings.getValue("HUDUIEnabled")) {
toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system");
button = toolBar.addButton({
objectName: buttonName,
imageURL: Script.resolvePath("assets/images/tools/snap.svg"),
visible: true,
alpha: 0.9,
});
} else {
tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
button = tablet.addButton({
icon: "icons/tablet-icons/snap-i.svg",
text: buttonName,
sortOrder: 5
});
}
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
var button = tablet.addButton({
icon: "icons/tablet-icons/snap-i.svg",
text: buttonName,
sortOrder: 5
});
function shouldOpenFeedAfterShare() {
var persisted = Settings.getValue('openFeedAfterShare', true); // might answer true, false, "true", or "false"
@ -63,42 +50,42 @@ function confirmShare(data) {
var isLoggedIn;
var needsLogin = false;
switch (message) {
case 'ready':
dialog.emitScriptEvent(data); // Send it.
outstanding = 0;
break;
case 'openSettings':
Desktop.show("hifi/dialogs/GeneralPreferencesDialog.qml", "GeneralPreferencesDialog");
break;
case 'setOpenFeedFalse':
Settings.setValue('openFeedAfterShare', false);
break;
case 'setOpenFeedTrue':
Settings.setValue('openFeedAfterShare', true);
break;
default:
dialog.webEventReceived.disconnect(onMessage);
dialog.close();
isLoggedIn = Account.isLoggedIn();
message.forEach(function (submessage) {
if (submessage.share && !isLoggedIn) {
needsLogin = true;
submessage.share = false;
}
if (submessage.share) {
print('sharing', submessage.localPath);
outstanding++;
Window.shareSnapshot(submessage.localPath, submessage.href);
} else {
print('not sharing', submessage.localPath);
}
});
if (!outstanding && shouldOpenFeedAfterShare()) {
showFeedWindow();
case 'ready':
dialog.emitScriptEvent(data); // Send it.
outstanding = 0;
break;
case 'openSettings':
Desktop.show("hifi/dialogs/GeneralPreferencesDialog.qml", "GeneralPreferencesDialog");
break;
case 'setOpenFeedFalse':
Settings.setValue('openFeedAfterShare', false);
break;
case 'setOpenFeedTrue':
Settings.setValue('openFeedAfterShare', true);
break;
default:
dialog.webEventReceived.disconnect(onMessage);
dialog.close();
isLoggedIn = Account.isLoggedIn();
message.forEach(function (submessage) {
if (submessage.share && !isLoggedIn) {
needsLogin = true;
submessage.share = false;
}
if (needsLogin) { // after the possible feed, so that the login is on top
Account.checkAndSignalForAccessToken();
if (submessage.share) {
print('sharing', submessage.localPath);
outstanding++;
Window.shareSnapshot(submessage.localPath, submessage.href);
} else {
print('not sharing', submessage.localPath);
}
});
if (!outstanding && shouldOpenFeedAfterShare()) {
showFeedWindow();
}
if (needsLogin) { // after the possible feed, so that the login is on top
Account.checkAndSignalForAccessToken();
}
}
}
dialog.webEventReceived.connect(onMessage);
@ -159,7 +146,7 @@ function isDomainOpen(id) {
var url = location.metaverseServerUrl + "/api/v1/user_stories?" + options.join('&');
request.open("GET", url, false);
request.send();
if (request.status != 200) {
if (request.status !== 200) {
return false;
}
var response = JSON.parse(request.response); // Not parsed for us.
@ -229,9 +216,6 @@ Script.scriptEnding.connect(function () {
if (tablet) {
tablet.removeButton(button);
}
if (toolBar) {
toolBar.removeButton(buttonName);
}
Window.snapshotShared.disconnect(snapshotShared);
Window.processingGif.disconnect(processingGif);
});

View file

@ -12,54 +12,27 @@
//
(function() { // BEGIN LOCAL_SCOPE
var gotoQmlSource = "TabletAddressDialog.qml";
var button;
var gotoQmlSource = "TabletAddressDialog.qml";
var buttonName = "GOTO";
var toolBar = null;
var tablet = null;
function onAddressBarShown(visible) {
if (toolBar) {
button.editProperties({isActive: visible});
}
}
function onClicked(){
if (toolBar) {
DialogsManager.toggleAddressBar();
} else {
tablet.loadQMLSource(gotoQmlSource);
}
tablet.loadQMLSource(gotoQmlSource);
}
if (Settings.getValue("HUDUIEnabled")) {
toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system");
button = toolBar.addButton({
objectName: buttonName,
imageURL: Script.resolvePath("assets/images/tools/directory.svg"),
visible: true,
alpha: 0.9
});
} else {
tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
button = tablet.addButton({
icon: "icons/tablet-icons/goto-i.svg",
activeIcon: "icons/tablet-icons/goto-a.svg",
text: buttonName,
sortOrder: 8
});
}
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
var button = tablet.addButton({
icon: "icons/tablet-icons/goto-i.svg",
activeIcon: "icons/tablet-icons/goto-a.svg",
text: buttonName,
sortOrder: 8
});
button.clicked.connect(onClicked);
DialogsManager.addressBarShown.connect(onAddressBarShown);
Script.scriptEnding.connect(function () {
button.clicked.disconnect(onClicked);
if (tablet) {
tablet.removeButton(button);
}
if (toolBar) {
toolBar.removeButton(buttonName);
}
DialogsManager.addressBarShown.disconnect(onAddressBarShown);
});
}()); // END LOCAL_SCOPE

View file

@ -52,6 +52,15 @@
}
function updateShowTablet() {
// close the WebTablet if it we go into toolbar mode.
var toolbarMode = Tablet.getTablet("com.highfidelity.interface.tablet.system").toolbarMode;
if (tabletShown && toolbarMode) {
hideTabletUI();
HMD.closeTablet();
return;
}
if (tabletShown) {
var MUTE_MICROPHONE_MENU_ITEM = "Mute Microphone";
var currentMicEnabled = !Menu.isOptionChecked(MUTE_MICROPHONE_MENU_ITEM);
@ -67,7 +76,7 @@
// other reason, close the tablet.
hideTabletUI();
HMD.closeTablet();
} else if (HMD.showTablet && !tabletShown) {
} else if (HMD.showTablet && !tabletShown && !toolbarMode) {
UserActivityLogger.openedTablet();
showTabletUI();
} else if (!HMD.showTablet && tabletShown) {