mirror of
https://github.com/lubosz/overte.git
synced 2025-04-12 10:02:25 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into skin
This commit is contained in:
commit
ec68eba3cb
156 changed files with 5177 additions and 2945 deletions
domain-server/src
interface
resources
controllers
icons
qml
AddressBarDialog.qmlAssetServer.qmlBrowser.qmlInfoView.qmlLoginDialog.qmlQmlWebWindow.qmlQmlWindow.qmlToolWindow.qmlUpdateDialog.qml
controls-uit
desktop
dialogs
hifi
Desktop.qml
dialogs
toolbars
windows-uit
windows
src
libraries
avatars/src
controllers/src/controllers
display-plugins/src/display-plugins
entities-renderer/src
entities/src
EntityItem.cppEntityItem.hEntityItemProperties.cppModelEntityItem.cppModelEntityItem.hParticleEffectEntityItem.cppParticleEffectEntityItem.hZoneEntityItem.cppZoneEntityItem.h
gl/src/gl
GLHelpers.cppGLHelpers.hOffscreenQmlSurface.cppOffscreenQmlSurface.hOglplusHelpers.cppOglplusHelpers.h
networking/src
AccountManager.cppAccountManager.hAddressManager.cppUserActivityLogger.cppUserActivityLogger.hUserActivityLoggerScriptingInterface.cppUserActivityLoggerScriptingInterface.h
udt
physics/src
|
@ -63,12 +63,16 @@ const QString DomainMetadata::Descriptors::Hours::CLOSE = "close";
|
|||
DomainMetadata::DomainMetadata(QObject* domainServer) : QObject(domainServer) {
|
||||
// set up the structure necessary for casting during parsing (see parseHours, esp.)
|
||||
_metadata[USERS] = QVariantMap {};
|
||||
_metadata[DESCRIPTORS] = QVariantMap {
|
||||
{ Descriptors::HOURS, QVariantMap {
|
||||
{ Descriptors::Hours::WEEKDAY, QVariantList { QVariantMap{} } },
|
||||
{ Descriptors::Hours::WEEKEND, QVariantList { QVariantMap{} } }
|
||||
} }
|
||||
};
|
||||
_metadata[DESCRIPTORS] = QVariantMap { {
|
||||
Descriptors::HOURS, QVariantMap {
|
||||
{ Descriptors::Hours::WEEKDAY, QVariantList {
|
||||
QVariantList{ QVariant{}, QVariant{} } }
|
||||
},
|
||||
{ Descriptors::Hours::WEEKEND, QVariantList {
|
||||
QVariantList{ QVariant{}, QVariant{} } }
|
||||
}
|
||||
}
|
||||
} };
|
||||
|
||||
assert(dynamic_cast<DomainServer*>(domainServer));
|
||||
DomainServer* server = static_cast<DomainServer*>(domainServer);
|
||||
|
@ -96,33 +100,37 @@ QJsonObject DomainMetadata::get(const QString& group) {
|
|||
return QJsonObject::fromVariantMap(_metadata[group].toMap());
|
||||
}
|
||||
|
||||
// merge delta into target
|
||||
// target should be of the form [ OpenTime, CloseTime ],
|
||||
// delta should be of the form [ { open: Time, close: Time } ]
|
||||
void parseHours(QVariant delta, QVariant& target) {
|
||||
using Hours = DomainMetadata::Descriptors::Hours;
|
||||
|
||||
// hours should be of the form [ { open: Time, close: Time } ]
|
||||
assert(target.canConvert<QVariantList>());
|
||||
auto& targetList = *static_cast<QVariantList*>(target.data());
|
||||
|
||||
// if/when multiple ranges are allowed, this list will need to be iterated
|
||||
assert(targetList[0].canConvert<QVariantMap>());
|
||||
auto& targetMap = *static_cast<QVariantMap*>(targetList[0].data());
|
||||
assert(targetList[0].canConvert<QVariantList>());
|
||||
auto& hours = *static_cast<QVariantList*>(targetList[0].data());
|
||||
|
||||
auto deltaMap = delta.toList()[0].toMap();
|
||||
if (deltaMap.isEmpty()) {
|
||||
auto deltaHours = delta.toList()[0].toMap();
|
||||
if (deltaHours.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// merge delta into base
|
||||
auto open = deltaMap.find(Hours::OPEN);
|
||||
if (open != deltaMap.end()) {
|
||||
targetMap[Hours::OPEN] = open.value();
|
||||
static const int OPEN_INDEX = 0;
|
||||
static const int CLOSE_INDEX = 1;
|
||||
auto open = deltaHours.find(Hours::OPEN);
|
||||
if (open != deltaHours.end()) {
|
||||
hours[OPEN_INDEX] = open.value();
|
||||
}
|
||||
assert(targetMap[Hours::OPEN].canConvert<QString>());
|
||||
auto close = deltaMap.find(Hours::CLOSE);
|
||||
if (close != deltaMap.end()) {
|
||||
targetMap[Hours::CLOSE] = close.value();
|
||||
assert(hours[OPEN_INDEX].canConvert<QString>());
|
||||
auto close = deltaHours.find(Hours::CLOSE);
|
||||
if (close != deltaHours.end()) {
|
||||
hours[CLOSE_INDEX] = close.value();
|
||||
}
|
||||
assert(targetMap[Hours::CLOSE].canConvert<QString>());
|
||||
assert(hours[CLOSE_INDEX].canConvert<QString>());
|
||||
}
|
||||
|
||||
void DomainMetadata::descriptorsChanged() {
|
||||
|
|
|
@ -1,18 +1,26 @@
|
|||
{
|
||||
"name": "Vive to Standard",
|
||||
"channels": [
|
||||
{ "from": "Vive.LY", "when": "Vive.LSOuter", "filters": ["invert"], "to": "Standard.LY" },
|
||||
{ "from": "Vive.LX", "when": "Vive.LSOuter", "to": "Standard.LX" },
|
||||
|
||||
{ "from": "Vive.LT", "to": "Standard.LT" },
|
||||
{ "from": "Vive.LY", "when": "Vive.LSY", "filters": ["invert"], "to": "Standard.LY" },
|
||||
{ "from": "Vive.LX", "when": "Vive.LSX", "to": "Standard.LX" },
|
||||
{
|
||||
"from": "Vive.LT", "to": "Standard.LT",
|
||||
"filters": [
|
||||
{ "type": "deadZone", "min": 0.05 }
|
||||
]
|
||||
},
|
||||
{ "from": "Vive.LeftGrip", "to": "Standard.LeftGrip" },
|
||||
{ "from": "Vive.LS", "to": "Standard.LS" },
|
||||
{ "from": "Vive.LSTouch", "to": "Standard.LSTouch" },
|
||||
|
||||
{ "from": "Vive.RY", "when": "Vive.RSOuter", "filters": ["invert"], "to": "Standard.RY" },
|
||||
{ "from": "Vive.RX", "when": "Vive.RSOuter", "to": "Standard.RX" },
|
||||
|
||||
{ "from": "Vive.RT", "to": "Standard.RT" },
|
||||
{ "from": "Vive.RY", "when": "Vive.RSY", "filters": ["invert"], "to": "Standard.RY" },
|
||||
{ "from": "Vive.RX", "when": "Vive.RSX", "to": "Standard.RX" },
|
||||
{
|
||||
"from": "Vive.RT", "to": "Standard.RT",
|
||||
"filters": [
|
||||
{ "type": "deadZone", "min": 0.05 }
|
||||
]
|
||||
},
|
||||
{ "from": "Vive.RightGrip", "to": "Standard.RightGrip" },
|
||||
{ "from": "Vive.RS", "to": "Standard.RS" },
|
||||
{ "from": "Vive.RSTouch", "to": "Standard.RSTouch" },
|
||||
|
|
105
interface/resources/icons/hud-01.svg
Normal file
105
interface/resources/icons/hud-01.svg
Normal file
|
@ -0,0 +1,105 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 50 150" style="enable-background:new 0 0 50 150;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{opacity:0.49;}
|
||||
.st1{fill:#1E1E1E;}
|
||||
.st2{opacity:0.5;}
|
||||
.st3{fill:#EAEAEA;}
|
||||
.st4{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g class="st0">
|
||||
<path class="st1" d="M50,146c0,2.2-1.8,4-4,4H4c-2.2,0-4-1.8-4-4v-42c0-2.2,1.8-4,4-4h42c2.2,0,4,1.8,4,4V146z"/>
|
||||
</g>
|
||||
<g class="st2">
|
||||
<g>
|
||||
<path class="st3" d="M25.1,109.5c-4.2,0-8,1.8-10.8,4.8c0,0-0.5,0.7,0.2,1.2c0.7,0.6,1.4-0.1,1.4-0.1c2.4-2.4,5.7-3.9,9.2-3.9
|
||||
c3.6,0,6.9,1.5,9.3,4.1c0,0,0.7,0.8,1.5,0.3c0.7-0.4,0.2-1.2,0.2-1.3C33.3,111.4,29.4,109.5,25.1,109.5z"/>
|
||||
<path class="st3" d="M35.4,116.3c-0.2,0-0.4,0-0.5-0.1c-0.2-0.1-0.5-0.3-0.6-0.4l0,0c-2.5-2.6-5.7-4-9.2-4c-3.4,0-6.6,1.4-9.1,3.9
|
||||
l0,0c-0.1,0.1-0.3,0.2-0.4,0.3c-0.5,0.2-0.9,0.2-1.3-0.2c-0.6-0.5-0.4-1.1-0.3-1.4c0-0.1,0.1-0.1,0.1-0.2l0,0
|
||||
c2.9-3.1,6.8-4.9,10.9-4.9c4.2,0,8.2,1.8,11.2,5.1c0.1,0.1,0.3,0.5,0.2,0.9c0,0.3-0.2,0.6-0.5,0.8
|
||||
C35.9,116.2,35.6,116.3,35.4,116.3z M34.7,115.4c0.1,0.1,0.6,0.6,1.1,0.2c0.1-0.1,0.2-0.2,0.2-0.3c0-0.3-0.1-0.5-0.1-0.6
|
||||
c-2.9-3.2-6.7-5-10.8-5c-4,0-7.7,1.7-10.6,4.7c-0.1,0.1-0.3,0.5,0.1,0.9c0.5,0.4,1,0,1.1-0.1c2.6-2.6,5.9-4,9.4-4
|
||||
C28.7,111.2,32.1,112.7,34.7,115.4L34.7,115.4z M36,114.7C36,114.7,36,114.7,36,114.7L36,114.7z M35.9,114.7
|
||||
C35.9,114.7,35.9,114.7,35.9,114.7L35.9,114.7z"/>
|
||||
</g>
|
||||
<path class="st3" d="M27.3,116l-1.5-0.9c-0.3-0.2-0.7-0.2-1,0l-1.5,0.9c-0.3,0.2-0.5,0.5-0.5,0.9l0,1.8c0,0.4,0.2,0.7,0.5,0.9
|
||||
l1.5,0.9c0.2,0.1,0.3,0.1,0.5,0.1c0.2,0,0.4,0,0.5-0.1l1.5-0.9c0.3-0.2,0.5-0.5,0.5-0.9l0-1.8C27.8,116.5,27.6,116.2,27.3,116z"/>
|
||||
<path class="st3" d="M31.2,125l-1.2-1.5c-0.2-0.3-0.5-0.4-0.8-0.4l-7.9,0c0,0,0,0,0,0c-0.3,0-0.6,0.1-0.8,0.4l-1.2,1.5
|
||||
c-0.3,0.4-0.3,0.9,0,1.3l1.2,1.5c0.2,0.3,0.5,0.4,0.8,0.4l7.9,0c0,0,0,0,0,0c0.3,0,0.6-0.1,0.8-0.4l1.2-1.5
|
||||
C31.5,126,31.5,125.4,31.2,125z"/>
|
||||
</g>
|
||||
<g class="st2">
|
||||
<path class="st3" d="M19.2,135.5v6.4H18v-2.7h-2.9v2.7h-1.2v-6.4h1.2v2.6H18v-2.6H19.2z"/>
|
||||
<path class="st3" d="M25.4,141c0.3,0,0.6-0.1,0.8-0.2c0.2-0.1,0.4-0.3,0.5-0.5c0.1-0.2,0.2-0.4,0.3-0.7c0.1-0.3,0.1-0.5,0.1-0.8
|
||||
v-3.4h1.3v3.4c0,0.5-0.1,0.9-0.2,1.3c-0.1,0.4-0.3,0.7-0.5,1c-0.2,0.3-0.5,0.5-0.9,0.7c-0.4,0.2-0.8,0.3-1.3,0.3
|
||||
c-0.5,0-1-0.1-1.4-0.3c-0.4-0.2-0.7-0.4-0.9-0.7c-0.2-0.3-0.4-0.6-0.5-1c-0.1-0.4-0.2-0.8-0.2-1.2v-3.4h1.3v3.4
|
||||
c0,0.3,0,0.5,0.1,0.8c0.1,0.3,0.1,0.5,0.3,0.7c0.1,0.2,0.3,0.4,0.5,0.5S25.1,141,25.4,141z"/>
|
||||
<path class="st3" d="M31.7,141.9v-6.4H34c0.5,0,1,0.1,1.4,0.3c0.4,0.2,0.7,0.4,1,0.7c0.3,0.3,0.5,0.6,0.6,1
|
||||
c0.1,0.4,0.2,0.8,0.2,1.2c0,0.5-0.1,0.9-0.2,1.3c-0.1,0.4-0.4,0.7-0.6,1c-0.3,0.3-0.6,0.5-1,0.6c-0.4,0.2-0.8,0.2-1.3,0.2H31.7z
|
||||
M35.9,138.7c0-0.3,0-0.6-0.1-0.8c-0.1-0.3-0.2-0.5-0.4-0.7c-0.2-0.2-0.4-0.3-0.6-0.4c-0.2-0.1-0.5-0.2-0.8-0.2h-1.1v4.2H34
|
||||
c0.3,0,0.6-0.1,0.8-0.2c0.2-0.1,0.4-0.3,0.6-0.4c0.2-0.2,0.3-0.4,0.4-0.7C35.8,139.3,35.9,139,35.9,138.7z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st1" d="M50,96c0,2.2-1.8,4-4,4H4c-2.2,0-4-1.8-4-4V54c0-2.2,1.8-4,4-4h42c2.2,0,4,1.8,4,4V96z"/>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st3" d="M25.1,59.5c-4.2,0-8,1.8-10.8,4.8c0,0-0.5,0.7,0.2,1.2c0.7,0.6,1.4-0.1,1.4-0.1c2.4-2.4,5.7-3.9,9.2-3.9
|
||||
c3.6,0,6.9,1.5,9.3,4.1c0,0,0.7,0.8,1.5,0.3c0.7-0.4,0.2-1.2,0.2-1.3C33.3,61.5,29.4,59.5,25.1,59.5z"/>
|
||||
<path class="st3" d="M35.4,66.3c-0.2,0-0.4,0-0.5-0.1c-0.2-0.1-0.5-0.3-0.6-0.4l0,0c-2.5-2.6-5.7-4-9.2-4c-3.4,0-6.6,1.4-9.1,3.9
|
||||
l0,0c-0.1,0.1-0.3,0.2-0.4,0.3c-0.5,0.2-0.9,0.2-1.3-0.2c-0.6-0.5-0.4-1.1-0.3-1.4c0-0.1,0.1-0.1,0.1-0.2l0,0
|
||||
c2.9-3.1,6.8-4.9,10.9-4.9c4.2,0,8.2,1.8,11.2,5.1c0.1,0.1,0.3,0.5,0.2,0.9c0,0.3-0.2,0.6-0.5,0.8C35.9,66.2,35.6,66.3,35.4,66.3z
|
||||
M34.7,65.5c0.1,0.1,0.6,0.6,1.1,0.2c0.1-0.1,0.2-0.2,0.2-0.3c0-0.3-0.1-0.5-0.1-0.6c-2.9-3.2-6.7-5-10.8-5c-4,0-7.7,1.7-10.6,4.7
|
||||
c-0.1,0.1-0.3,0.5,0.1,0.9c0.5,0.4,1,0,1.1-0.1c2.6-2.6,5.9-4,9.4-4C28.7,61.3,32.1,62.7,34.7,65.5L34.7,65.5z M36,64.7
|
||||
C36,64.7,36,64.7,36,64.7L36,64.7z M35.9,64.7C35.9,64.7,35.9,64.7,35.9,64.7L35.9,64.7z"/>
|
||||
</g>
|
||||
<path class="st3" d="M27.3,66l-1.5-0.9c-0.3-0.2-0.7-0.2-1,0L23.2,66c-0.3,0.2-0.5,0.5-0.5,0.9l0,1.8c0,0.4,0.2,0.7,0.5,0.9
|
||||
l1.5,0.9c0.2,0.1,0.3,0.1,0.5,0.1c0.2,0,0.4,0,0.5-0.1l1.5-0.9c0.3-0.2,0.5-0.5,0.5-0.9l0-1.8C27.8,66.5,27.6,66.2,27.3,66z"/>
|
||||
<path class="st3" d="M31.2,75.1L30,73.5c-0.2-0.3-0.5-0.4-0.8-0.4l-7.9,0c0,0,0,0,0,0c-0.3,0-0.6,0.1-0.8,0.4l-1.2,1.5
|
||||
c-0.3,0.4-0.3,0.9,0,1.3l1.2,1.5c0.2,0.3,0.5,0.4,0.8,0.4l7.9,0c0,0,0,0,0,0c0.3,0,0.6-0.1,0.8-0.4l1.2-1.5
|
||||
C31.5,76,31.5,75.5,31.2,75.1z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st3" d="M19.2,85.5v6.4H18v-2.7h-2.9v2.7h-1.2v-6.4h1.2v2.6H18v-2.6H19.2z"/>
|
||||
<path class="st3" d="M25.4,91c0.3,0,0.6-0.1,0.8-0.2s0.4-0.3,0.5-0.5c0.1-0.2,0.2-0.4,0.3-0.7s0.1-0.5,0.1-0.8v-3.4h1.3v3.4
|
||||
c0,0.5-0.1,0.9-0.2,1.3c-0.1,0.4-0.3,0.7-0.5,1c-0.2,0.3-0.5,0.5-0.9,0.7c-0.4,0.2-0.8,0.3-1.3,0.3c-0.5,0-1-0.1-1.4-0.3
|
||||
c-0.4-0.2-0.7-0.4-0.9-0.7c-0.2-0.3-0.4-0.6-0.5-1c-0.1-0.4-0.2-0.8-0.2-1.2v-3.4h1.3v3.4c0,0.3,0,0.5,0.1,0.8
|
||||
c0.1,0.3,0.1,0.5,0.3,0.7c0.1,0.2,0.3,0.4,0.5,0.5S25.1,91,25.4,91z"/>
|
||||
<path class="st3" d="M31.7,91.9v-6.4H34c0.5,0,1,0.1,1.4,0.3c0.4,0.2,0.7,0.4,1,0.7c0.3,0.3,0.5,0.6,0.6,1c0.1,0.4,0.2,0.8,0.2,1.2
|
||||
c0,0.5-0.1,0.9-0.2,1.3c-0.1,0.4-0.4,0.7-0.6,1c-0.3,0.3-0.6,0.5-1,0.6c-0.4,0.2-0.8,0.2-1.3,0.2H31.7z M35.9,88.7
|
||||
c0-0.3,0-0.6-0.1-0.8c-0.1-0.3-0.2-0.5-0.4-0.7c-0.2-0.2-0.4-0.3-0.6-0.4c-0.2-0.1-0.5-0.2-0.8-0.2h-1.1v4.2H34
|
||||
c0.3,0,0.6-0.1,0.8-0.2c0.2-0.1,0.4-0.3,0.6-0.4c0.2-0.2,0.3-0.4,0.4-0.7C35.8,89.3,35.9,89,35.9,88.7z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st4" d="M50,46.1c0,2.2-1.8,4-4,4H4c-2.2,0-4-1.8-4-4v-42c0-2.2,1.8-4,4-4h42c2.2,0,4,1.8,4,4V46.1z"/>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path d="M25.1,9.6c-4.2,0-8,1.8-10.8,4.8c0,0-0.5,0.7,0.2,1.2c0.7,0.6,1.4-0.1,1.4-0.1c2.4-2.4,5.7-3.9,9.2-3.9
|
||||
c3.6,0,6.9,1.5,9.3,4.1c0,0,0.7,0.8,1.5,0.3c0.7-0.4,0.2-1.2,0.2-1.3C33.3,11.5,29.4,9.6,25.1,9.6z"/>
|
||||
<path d="M35.4,16.3c-0.2,0-0.4,0-0.5-0.1c-0.2-0.1-0.5-0.3-0.6-0.4l0,0c-2.5-2.6-5.7-4-9.2-4c-3.4,0-6.6,1.4-9.1,3.9l0,0
|
||||
c-0.1,0.1-0.3,0.2-0.4,0.3c-0.5,0.2-0.9,0.2-1.3-0.2c-0.6-0.5-0.4-1.1-0.3-1.4c0-0.1,0.1-0.1,0.1-0.2l0,0
|
||||
c2.9-3.1,6.8-4.9,10.9-4.9c4.2,0,8.2,1.8,11.2,5.1c0.1,0.1,0.3,0.5,0.2,0.9c0,0.3-0.2,0.6-0.5,0.8C35.9,16.3,35.6,16.3,35.4,16.3z
|
||||
M34.7,15.5c0.1,0.1,0.6,0.6,1.1,0.2c0.1-0.1,0.2-0.2,0.2-0.3c0-0.3-0.1-0.5-0.1-0.6c-2.9-3.2-6.7-5-10.8-5c-4,0-7.7,1.7-10.6,4.7
|
||||
c-0.1,0.1-0.3,0.5,0.1,0.9c0.5,0.4,1,0,1.1-0.1c2.6-2.6,5.9-4,9.4-4C28.7,11.3,32.1,12.8,34.7,15.5L34.7,15.5z M36,14.8
|
||||
C36,14.8,36,14.8,36,14.8L36,14.8z M35.9,14.8C35.9,14.8,35.9,14.8,35.9,14.8L35.9,14.8z"/>
|
||||
</g>
|
||||
<path d="M27.3,16l-1.5-0.9c-0.3-0.2-0.7-0.2-1,0L23.2,16c-0.3,0.2-0.5,0.5-0.5,0.9l0,1.8c0,0.4,0.2,0.7,0.5,0.9l1.5,0.9
|
||||
c0.2,0.1,0.3,0.1,0.5,0.1c0.2,0,0.4,0,0.5-0.1l1.5-0.9c0.3-0.2,0.5-0.5,0.5-0.9l0-1.8C27.8,16.6,27.6,16.2,27.3,16z"/>
|
||||
<path d="M31.2,25.1L30,23.6c-0.2-0.3-0.5-0.4-0.8-0.4l-7.9,0c0,0,0,0,0,0c-0.3,0-0.6,0.1-0.8,0.4l-1.2,1.5c-0.3,0.4-0.3,0.9,0,1.3
|
||||
l1.2,1.5c0.2,0.3,0.5,0.4,0.8,0.4l7.9,0c0,0,0,0,0,0c0.3,0,0.6-0.1,0.8-0.4l1.2-1.5C31.5,26,31.5,25.5,31.2,25.1z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path d="M19.2,35.6V42H18v-2.7h-2.9V42h-1.2v-6.4h1.2v2.6H18v-2.6H19.2z"/>
|
||||
<path d="M25.4,41c0.3,0,0.6-0.1,0.8-0.2c0.2-0.1,0.4-0.3,0.5-0.5c0.1-0.2,0.2-0.4,0.3-0.7c0.1-0.3,0.1-0.5,0.1-0.8v-3.4h1.3v3.4
|
||||
c0,0.5-0.1,0.9-0.2,1.3c-0.1,0.4-0.3,0.7-0.5,1c-0.2,0.3-0.5,0.5-0.9,0.7c-0.4,0.2-0.8,0.3-1.3,0.3c-0.5,0-1-0.1-1.4-0.3
|
||||
c-0.4-0.2-0.7-0.4-0.9-0.7c-0.2-0.3-0.4-0.6-0.5-1c-0.1-0.4-0.2-0.8-0.2-1.2v-3.4h1.3v3.4c0,0.3,0,0.5,0.1,0.8
|
||||
c0.1,0.3,0.1,0.5,0.3,0.7c0.1,0.2,0.3,0.4,0.5,0.5C24.9,41,25.1,41,25.4,41z"/>
|
||||
<path d="M31.7,42v-6.4H34c0.5,0,1,0.1,1.4,0.3c0.4,0.2,0.7,0.4,1,0.7c0.3,0.3,0.5,0.6,0.6,1c0.1,0.4,0.2,0.8,0.2,1.2
|
||||
c0,0.5-0.1,0.9-0.2,1.3c-0.1,0.4-0.4,0.7-0.6,1c-0.3,0.3-0.6,0.5-1,0.6C34.9,41.9,34.5,42,34,42H31.7z M35.9,38.8
|
||||
c0-0.3,0-0.6-0.1-0.8c-0.1-0.3-0.2-0.5-0.4-0.7c-0.2-0.2-0.4-0.3-0.6-0.4c-0.2-0.1-0.5-0.2-0.8-0.2h-1.1v4.2H34
|
||||
c0.3,0,0.6-0.1,0.8-0.2c0.2-0.1,0.4-0.3,0.6-0.4c0.2-0.2,0.3-0.4,0.4-0.7C35.8,39.3,35.9,39.1,35.9,38.8z"/>
|
||||
</g>
|
||||
</svg>
|
After (image error) Size: 8 KiB |
|
@ -20,9 +20,10 @@ Window {
|
|||
|
||||
objectName: "AddressBarDialog"
|
||||
frame: HiddenFrame {}
|
||||
hideBackground: true
|
||||
|
||||
visible: false
|
||||
destroyOnInvisible: false
|
||||
shown: false
|
||||
destroyOnHidden: false
|
||||
resizable: false
|
||||
scale: 1.25 // Make this dialog a little larger than normal
|
||||
|
||||
|
@ -145,14 +146,14 @@ Window {
|
|||
if (addressLine.text !== "") {
|
||||
addressBarDialog.loadAddress(addressLine.text)
|
||||
}
|
||||
root.visible = false;
|
||||
root.shown = false;
|
||||
}
|
||||
|
||||
Keys.onPressed: {
|
||||
switch (event.key) {
|
||||
case Qt.Key_Escape:
|
||||
case Qt.Key_Back:
|
||||
root.visible = false
|
||||
root.shown = false
|
||||
event.accepted = true
|
||||
break
|
||||
case Qt.Key_Enter:
|
||||
|
|
|
@ -15,15 +15,15 @@ import Qt.labs.settings 1.0
|
|||
|
||||
import "styles-uit"
|
||||
import "controls-uit" as HifiControls
|
||||
import "windows-uit"
|
||||
import "windows"
|
||||
import "dialogs"
|
||||
|
||||
Window {
|
||||
ScrollingWindow {
|
||||
id: root
|
||||
objectName: "AssetServer"
|
||||
title: "Asset Browser"
|
||||
resizable: true
|
||||
destroyOnInvisible: true
|
||||
destroyOnHidden: true
|
||||
implicitWidth: 384; implicitHeight: 640
|
||||
minSize: Qt.vector2d(200, 300)
|
||||
|
||||
|
|
|
@ -2,22 +2,24 @@ import QtQuick 2.3
|
|||
import QtQuick.Controls 1.2
|
||||
import QtWebEngine 1.1
|
||||
|
||||
import "controls"
|
||||
import "styles"
|
||||
import "controls-uit"
|
||||
import "styles-uit"
|
||||
import "windows"
|
||||
|
||||
Window {
|
||||
ScrollingWindow {
|
||||
id: root
|
||||
HifiConstants { id: hifi }
|
||||
title: "Browser"
|
||||
resizable: true
|
||||
destroyOnInvisible: true
|
||||
destroyOnHidden: true
|
||||
width: 800
|
||||
height: 600
|
||||
property alias webView: webview
|
||||
|
||||
x: 100
|
||||
y: 100
|
||||
|
||||
Component.onCompleted: {
|
||||
visible = true
|
||||
shown = true
|
||||
addressBar.text = webview.url
|
||||
}
|
||||
|
||||
|
@ -30,15 +32,9 @@ Window {
|
|||
|
||||
Item {
|
||||
id:item
|
||||
anchors.fill: parent
|
||||
Rectangle {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: webview.top
|
||||
color: "white"
|
||||
}
|
||||
|
||||
width: pane.contentWidth
|
||||
implicitHeight: pane.scrollHeight
|
||||
|
||||
Row {
|
||||
id: buttons
|
||||
spacing: 4
|
||||
|
@ -46,25 +42,37 @@ Window {
|
|||
anchors.topMargin: 8
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 8
|
||||
FontAwesome {
|
||||
id: back; text: "\uf0a8"; size: 48; enabled: webview.canGoBack;
|
||||
HiFiGlyphs {
|
||||
id: back;
|
||||
enabled: webview.canGoBack;
|
||||
text: hifi.glyphs.backward
|
||||
color: enabled ? hifi.colors.text : hifi.colors.disabledText
|
||||
size: 48
|
||||
MouseArea { anchors.fill: parent; onClicked: webview.goBack() }
|
||||
}
|
||||
FontAwesome {
|
||||
id: forward; text: "\uf0a9"; size: 48; enabled: webview.canGoForward;
|
||||
|
||||
HiFiGlyphs {
|
||||
id: forward;
|
||||
enabled: webview.canGoForward;
|
||||
text: hifi.glyphs.forward
|
||||
color: enabled ? hifi.colors.text : hifi.colors.disabledText
|
||||
MouseArea { anchors.fill: parent; onClicked: webview.goBack() }
|
||||
size: 48
|
||||
MouseArea { anchors.fill: parent; onClicked: webview.goForward() }
|
||||
}
|
||||
FontAwesome {
|
||||
id: reload; size: 48; text: webview.loading ? "\uf057" : "\uf021"
|
||||
MouseArea { anchors.fill: parent; onClicked: webview.loading ? webview.stop() : webview.reload() }
|
||||
|
||||
HiFiGlyphs {
|
||||
id: reload;
|
||||
enabled: webview.canGoForward;
|
||||
text: webview.loading ? hifi.glyphs.close : hifi.glyphs.reload
|
||||
color: enabled ? hifi.colors.text : hifi.colors.disabledText
|
||||
size: 48
|
||||
MouseArea { anchors.fill: parent; onClicked: webview.goForward() }
|
||||
}
|
||||
}
|
||||
|
||||
Border {
|
||||
Item {
|
||||
id: border
|
||||
height: 48
|
||||
radius: 8
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 8
|
||||
anchors.right: parent.right
|
||||
|
@ -86,15 +94,18 @@ Window {
|
|||
onSourceChanged: console.log("Icon url: " + source)
|
||||
}
|
||||
}
|
||||
|
||||
TextInput {
|
||||
|
||||
TextField {
|
||||
id: addressBar
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 8
|
||||
anchors.left: barIcon.right
|
||||
anchors.leftMargin: 0
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
focus: true
|
||||
colorScheme: hifi.colorSchemes.dark
|
||||
placeholderText: "Enter URL"
|
||||
Component.onCompleted: scriptsModel.filterRegExp = new RegExp("^.*$", "i")
|
||||
Keys.onPressed: {
|
||||
switch(event.key) {
|
||||
case Qt.Key_Enter:
|
||||
|
@ -110,7 +121,7 @@ Window {
|
|||
}
|
||||
}
|
||||
|
||||
WebView {
|
||||
WebEngineView {
|
||||
id: webview
|
||||
url: "http://highfidelity.com"
|
||||
anchors.top: buttons.bottom
|
||||
|
@ -119,7 +130,7 @@ Window {
|
|||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
onLoadingChanged: {
|
||||
if (loadRequest.status == WebEngineView.LoadSucceededStatus) {
|
||||
if (loadRequest.status === WebEngineView.LoadSucceededStatus) {
|
||||
addressBar.text = loadRequest.url
|
||||
}
|
||||
}
|
||||
|
@ -127,7 +138,7 @@ Window {
|
|||
console.log("New icon: " + icon)
|
||||
}
|
||||
|
||||
profile: desktop.browserProfile
|
||||
//profile: desktop.browserProfile
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -12,9 +12,9 @@ import QtQuick 2.5
|
|||
import Hifi 1.0 as Hifi
|
||||
|
||||
import "controls-uit"
|
||||
import "windows-uit" as Windows
|
||||
import "windows" as Windows
|
||||
|
||||
Windows.Window {
|
||||
Windows.ScrollingWindow {
|
||||
id: root
|
||||
width: 800
|
||||
height: 800
|
||||
|
|
|
@ -14,7 +14,7 @@ import "controls"
|
|||
import "styles"
|
||||
import "windows"
|
||||
|
||||
Window {
|
||||
ScrollingWindow {
|
||||
id: root
|
||||
HifiConstants { id: hifi }
|
||||
objectName: "LoginDialog"
|
||||
|
@ -22,8 +22,9 @@ Window {
|
|||
width: loginDialog.implicitWidth
|
||||
// FIXME make movable
|
||||
anchors.centerIn: parent
|
||||
destroyOnInvisible: false
|
||||
visible: false
|
||||
destroyOnHidden: false
|
||||
hideBackground: true
|
||||
shown: false
|
||||
|
||||
LoginDialog {
|
||||
id: loginDialog
|
||||
|
@ -268,8 +269,8 @@ Window {
|
|||
}
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if (!visible) {
|
||||
onShownChanged: {
|
||||
if (!shown) {
|
||||
username.text = ""
|
||||
password.text = ""
|
||||
loginDialog.statusText = ""
|
||||
|
@ -282,7 +283,7 @@ Window {
|
|||
switch (event.key) {
|
||||
case Qt.Key_Escape:
|
||||
case Qt.Key_Back:
|
||||
root.visible = false;
|
||||
root.shown = false;
|
||||
event.accepted = true;
|
||||
break;
|
||||
|
||||
|
|
|
@ -13,16 +13,16 @@ import QtQuick.Controls 1.4
|
|||
import QtWebEngine 1.1
|
||||
import QtWebChannel 1.0
|
||||
|
||||
import "windows-uit" as Windows
|
||||
import "windows" as Windows
|
||||
import "controls-uit" as Controls
|
||||
import "styles-uit"
|
||||
|
||||
Windows.Window {
|
||||
Windows.ScrollingWindow {
|
||||
id: root
|
||||
HifiConstants { id: hifi }
|
||||
title: "WebWindow"
|
||||
resizable: true
|
||||
visible: false
|
||||
shown: false
|
||||
// Don't destroy on close... otherwise the JS/C++ will have a dangling pointer
|
||||
destroyOnCloseButton: false
|
||||
property alias source: webview.url
|
||||
|
|
|
@ -14,7 +14,7 @@ Windows.Window {
|
|||
HifiConstants { id: hifi }
|
||||
title: "QmlWindow"
|
||||
resizable: true
|
||||
visible: false
|
||||
shown: false
|
||||
focus: true
|
||||
property var channel;
|
||||
// Don't destroy on close... otherwise the JS/C++ will have a dangling pointer
|
||||
|
|
|
@ -15,18 +15,18 @@ import QtWebEngine 1.1
|
|||
import QtWebChannel 1.0
|
||||
import Qt.labs.settings 1.0
|
||||
|
||||
import "windows-uit"
|
||||
import "windows"
|
||||
import "controls-uit"
|
||||
import "styles-uit"
|
||||
|
||||
Window {
|
||||
ScrollingWindow {
|
||||
id: toolWindow
|
||||
resizable: true
|
||||
objectName: "ToolWindow"
|
||||
destroyOnCloseButton: false
|
||||
destroyOnInvisible: false
|
||||
destroyOnHidden: false
|
||||
closable: true
|
||||
visible: false
|
||||
shown: false
|
||||
title: "Edit"
|
||||
property alias tabView: tabView
|
||||
implicitWidth: 520; implicitHeight: 695
|
||||
|
@ -142,7 +142,7 @@ Window {
|
|||
return;
|
||||
}
|
||||
}
|
||||
visible = false;
|
||||
shown = false;
|
||||
}
|
||||
|
||||
function findIndexForUrl(source) {
|
||||
|
@ -172,7 +172,7 @@ Window {
|
|||
|
||||
var tab = tabView.getTab(index);
|
||||
if (newVisible) {
|
||||
toolWindow.visible = true
|
||||
toolWindow.shown = true
|
||||
tab.enabled = true
|
||||
} else {
|
||||
tab.enabled = false;
|
||||
|
|
|
@ -3,11 +3,11 @@ import QtQuick 2.3
|
|||
import QtQuick.Controls 1.3
|
||||
import QtQuick.Controls.Styles 1.3
|
||||
import QtGraphicalEffects 1.0
|
||||
import "controls"
|
||||
import "styles"
|
||||
import "controls-uit"
|
||||
import "styles-uit"
|
||||
import "windows"
|
||||
|
||||
Window {
|
||||
ScrollingWindow {
|
||||
id: root
|
||||
HifiConstants { id: hifi }
|
||||
objectName: "UpdateDialog"
|
||||
|
|
|
@ -15,7 +15,7 @@ import QtQuick.XmlListModel 2.0
|
|||
|
||||
import "../styles-uit"
|
||||
import "../controls-uit" as HifiControls
|
||||
import "../windows-uit"
|
||||
import "../windows"
|
||||
import "../hifi/models"
|
||||
|
||||
TableView {
|
||||
|
|
|
@ -52,6 +52,7 @@ FocusScope {
|
|||
readonly property real menu: 8000
|
||||
}
|
||||
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
|
||||
|
@ -64,7 +65,7 @@ FocusScope {
|
|||
|
||||
var oldChildren = expectedChildren;
|
||||
var newChildren = d.getRepositionChildren();
|
||||
if (oldRecommendedRect != Qt.rect(0,0,0,0)
|
||||
if (oldRecommendedRect != Qt.rect(0,0,0,0) && oldRecommendedRect != Qt.rect(0,0,1,1)
|
||||
&& (oldRecommendedRect != newRecommendedRect
|
||||
|| oldChildren != newChildren)
|
||||
) {
|
||||
|
@ -93,6 +94,17 @@ FocusScope {
|
|||
return item;
|
||||
}
|
||||
|
||||
function findMatchingChildren(item, predicate) {
|
||||
var results = [];
|
||||
for (var i in item.children) {
|
||||
var child = item.children[i];
|
||||
if (predicate(child)) {
|
||||
results.push(child);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
function isTopLevelWindow(item) {
|
||||
return item.topLevelWindow;
|
||||
}
|
||||
|
@ -106,19 +118,9 @@ FocusScope {
|
|||
}
|
||||
|
||||
function getTopLevelWindows(predicate) {
|
||||
var currentWindows = [];
|
||||
if (!desktop) {
|
||||
console.log("Could not find desktop for " + item)
|
||||
return currentWindows;
|
||||
}
|
||||
|
||||
for (var i = 0; i < desktop.children.length; ++i) {
|
||||
var child = desktop.children[i];
|
||||
if (isTopLevelWindow(child) && (!predicate || predicate(child))) {
|
||||
currentWindows.push(child)
|
||||
}
|
||||
}
|
||||
return currentWindows;
|
||||
return findMatchingChildren(desktop, function(child) {
|
||||
return (isTopLevelWindow(child) && (!predicate || predicate(child)));
|
||||
});
|
||||
}
|
||||
|
||||
function getDesktopWindow(item) {
|
||||
|
@ -227,19 +229,9 @@ FocusScope {
|
|||
}
|
||||
|
||||
function getRepositionChildren(predicate) {
|
||||
var currentWindows = [];
|
||||
if (!desktop) {
|
||||
console.log("Could not find desktop");
|
||||
return currentWindows;
|
||||
}
|
||||
|
||||
for (var i = 0; i < desktop.children.length; ++i) {
|
||||
var child = desktop.children[i];
|
||||
if (child.shouldReposition === true && (!predicate || predicate(child))) {
|
||||
currentWindows.push(child)
|
||||
}
|
||||
}
|
||||
return currentWindows;
|
||||
return findMatchingChildren(desktop, function(child) {
|
||||
return (child.shouldReposition === true && (!predicate || predicate(child)));
|
||||
});
|
||||
}
|
||||
|
||||
function repositionAll() {
|
||||
|
@ -265,6 +257,63 @@ FocusScope {
|
|||
}
|
||||
}
|
||||
|
||||
property bool pinned: false
|
||||
property var hiddenChildren: []
|
||||
|
||||
function togglePinned() {
|
||||
pinned = !pinned
|
||||
}
|
||||
|
||||
function setPinned(newPinned) {
|
||||
pinned = newPinned
|
||||
}
|
||||
|
||||
property real unpinnedAlpha: 1.0;
|
||||
|
||||
Behavior on unpinnedAlpha {
|
||||
NumberAnimation {
|
||||
easing.type: Easing.Linear;
|
||||
duration: 300
|
||||
}
|
||||
}
|
||||
|
||||
state: "NORMAL"
|
||||
states: [
|
||||
State {
|
||||
name: "NORMAL"
|
||||
PropertyChanges { target: desktop; unpinnedAlpha: 1.0 }
|
||||
},
|
||||
State {
|
||||
name: "PINNED"
|
||||
PropertyChanges { target: desktop; unpinnedAlpha: 0.0 }
|
||||
}
|
||||
]
|
||||
|
||||
transitions: [
|
||||
Transition {
|
||||
NumberAnimation { properties: "unpinnedAlpha"; duration: 300 }
|
||||
}
|
||||
]
|
||||
|
||||
onPinnedChanged: {
|
||||
if (pinned) {
|
||||
nullFocus.focus = true;
|
||||
nullFocus.forceActiveFocus();
|
||||
|
||||
// recalculate our non-pinned children
|
||||
hiddenChildren = d.findMatchingChildren(desktop, function(child){
|
||||
return !d.isTopLevelWindow(child) && child.visible && !child.pinned;
|
||||
});
|
||||
|
||||
hiddenChildren.forEach(function(child){
|
||||
child.opacity = Qt.binding(function(){ return desktop.unpinnedAlpha });
|
||||
});
|
||||
}
|
||||
state = pinned ? "PINNED" : "NORMAL"
|
||||
}
|
||||
|
||||
onShowDesktop: pinned = false
|
||||
|
||||
function raise(item) {
|
||||
var targetWindow = d.getDesktopWindow(item);
|
||||
if (!targetWindow) {
|
||||
|
@ -422,7 +471,6 @@ FocusScope {
|
|||
event.accepted = false;
|
||||
}
|
||||
|
||||
|
||||
function unfocusWindows() {
|
||||
var windows = d.getTopLevelWindows();
|
||||
for (var i = 0; i < windows.length; ++i) {
|
||||
|
@ -433,6 +481,8 @@ FocusScope {
|
|||
|
||||
FocusHack { id: focusHack; }
|
||||
|
||||
FocusScope { id: nullFocus; }
|
||||
|
||||
Rectangle {
|
||||
id: focusDebugger;
|
||||
objectName: "focusDebugger"
|
||||
|
|
|
@ -18,7 +18,7 @@ import QtQuick.Dialogs 1.2 as OriginalDialogs
|
|||
import ".."
|
||||
import "../controls-uit"
|
||||
import "../styles-uit"
|
||||
import "../windows-uit"
|
||||
import "../windows"
|
||||
|
||||
import "fileDialog"
|
||||
|
||||
|
@ -729,7 +729,7 @@ ModalWindow {
|
|||
Action {
|
||||
id: cancelAction
|
||||
text: "Cancel"
|
||||
onTriggered: { canceled(); root.visible = false; }
|
||||
onTriggered: { canceled(); root.shown = false; }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ import QtQuick.Dialogs 1.2 as OriginalDialogs
|
|||
|
||||
import "../controls-uit"
|
||||
import "../styles-uit"
|
||||
import "../windows-uit"
|
||||
import "../windows"
|
||||
|
||||
import "messageDialog"
|
||||
|
||||
|
@ -24,7 +24,7 @@ ModalWindow {
|
|||
implicitWidth: 640
|
||||
implicitHeight: 320
|
||||
destroyOnCloseButton: true
|
||||
destroyOnInvisible: true
|
||||
destroyOnHidden: true
|
||||
visible: true
|
||||
|
||||
signal selected(int button);
|
||||
|
|
|
@ -13,14 +13,14 @@ import QtQuick.Controls 1.4
|
|||
|
||||
import "../controls-uit" as HifiControls
|
||||
import "../styles-uit"
|
||||
import "../windows-uit"
|
||||
import "../windows"
|
||||
import "preferences"
|
||||
|
||||
Window {
|
||||
ScrollingWindow {
|
||||
id: root
|
||||
title: "Preferences"
|
||||
resizable: true
|
||||
destroyOnInvisible: true
|
||||
destroyOnHidden: true
|
||||
width: 500
|
||||
height: 577
|
||||
property var sections: []
|
||||
|
|
|
@ -14,7 +14,7 @@ import QtQuick.Dialogs 1.2 as OriginalDialogs
|
|||
|
||||
import "../controls-uit"
|
||||
import "../styles-uit"
|
||||
import "../windows-uit"
|
||||
import "../windows"
|
||||
|
||||
ModalWindow {
|
||||
id: root
|
||||
|
|
|
@ -12,7 +12,7 @@ import QtQuick 2.5
|
|||
import QtQuick.Controls 1.4
|
||||
import QtWebEngine 1.1
|
||||
|
||||
import "../../windows-uit" as Windows
|
||||
import "../../windows" as Windows
|
||||
import "../../controls-uit" as Controls
|
||||
import "../../styles-uit"
|
||||
|
||||
|
@ -23,15 +23,10 @@ Windows.Window {
|
|||
resizable: true
|
||||
modality: Qt.ApplicationModal
|
||||
|
||||
Item {
|
||||
width: pane.contentWidth
|
||||
implicitHeight: pane.scrollHeight
|
||||
|
||||
Controls.WebView {
|
||||
id: webview
|
||||
anchors.fill: parent
|
||||
url: "https://metaverse.highfidelity.com/marketplace?category=avatars"
|
||||
focus: true
|
||||
}
|
||||
Controls.WebView {
|
||||
id: webview
|
||||
anchors.fill: parent
|
||||
url: "https://metaverse.highfidelity.com/marketplace?category=avatars"
|
||||
focus: true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,18 @@
|
|||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtWebEngine 1.1;
|
||||
import Qt.labs.settings 1.0
|
||||
|
||||
import "../desktop"
|
||||
import "../desktop" as OriginalDesktop
|
||||
import ".."
|
||||
import "."
|
||||
import "./toolbars"
|
||||
|
||||
Desktop {
|
||||
OriginalDesktop.Desktop {
|
||||
id: desktop
|
||||
|
||||
MouseArea {
|
||||
id: hoverWatch
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
propagateComposedEvents: true
|
||||
|
@ -18,13 +22,6 @@ Desktop {
|
|||
acceptedButtons: Qt.NoButton
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
WebEngine.settings.javascriptCanOpenWindows = true;
|
||||
WebEngine.settings.javascriptCanAccessClipboard = false;
|
||||
WebEngine.settings.spatialNavigationEnabled = false;
|
||||
WebEngine.settings.localContentCanAccessRemoteUrls = true;
|
||||
}
|
||||
|
||||
// The tool window, one instance
|
||||
property alias toolWindow: toolWindow
|
||||
ToolWindow { id: toolWindow }
|
||||
|
@ -47,7 +44,42 @@ Desktop {
|
|||
}
|
||||
}
|
||||
|
||||
property var toolbars: ({})
|
||||
Component { id: toolbarBuilder; Toolbar { } }
|
||||
|
||||
Component.onCompleted: {
|
||||
WebEngine.settings.javascriptCanOpenWindows = true;
|
||||
WebEngine.settings.javascriptCanAccessClipboard = false;
|
||||
WebEngine.settings.spatialNavigationEnabled = false;
|
||||
WebEngine.settings.localContentCanAccessRemoteUrls = true;
|
||||
|
||||
var sysToolbar = desktop.getToolbar("com.highfidelity.interface.toolbar.system");
|
||||
var toggleHudButton = sysToolbar.addButton({
|
||||
objectName: "hudToggle",
|
||||
imageURL: "../../../icons/hud-01.svg",
|
||||
visible: true,
|
||||
pinned: true,
|
||||
});
|
||||
|
||||
toggleHudButton.yOffset = Qt.binding(function(){
|
||||
return desktop.pinned ? 50 : 0
|
||||
});
|
||||
toggleHudButton.clicked.connect(function(){
|
||||
console.log("Clicked on hud button")
|
||||
var overlayMenuItem = "Overlays"
|
||||
MenuInterface.setIsOptionChecked(overlayMenuItem, !MenuInterface.isOptionChecked(overlayMenuItem));
|
||||
});
|
||||
}
|
||||
|
||||
// Create or fetch a toolbar with the given name
|
||||
function getToolbar(name) {
|
||||
var result = toolbars[name];
|
||||
if (!result) {
|
||||
result = toolbars[name] = toolbarBuilder.createObject(desktop, {});
|
||||
result.objectName = name;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -6,17 +6,17 @@ import QtQuick.Controls.Styles 1.4
|
|||
|
||||
import "../../styles-uit"
|
||||
import "../../controls-uit" as HifiControls
|
||||
import "../../windows-uit"
|
||||
import "../../windows"
|
||||
import "attachments"
|
||||
|
||||
Window {
|
||||
ScrollingWindow {
|
||||
id: root
|
||||
title: "Attachments"
|
||||
objectName: "AttachmentsDialog"
|
||||
width: 600
|
||||
height: 600
|
||||
resizable: true
|
||||
destroyOnInvisible: true
|
||||
destroyOnHidden: true
|
||||
minSize: Qt.vector2d(400, 500)
|
||||
|
||||
HifiConstants { id: hifi }
|
||||
|
|
|
@ -9,9 +9,9 @@ import "../models"
|
|||
|
||||
import "../../styles-uit"
|
||||
import "../../controls-uit" as HifiControls
|
||||
import "../../windows-uit"
|
||||
import "../../windows"
|
||||
|
||||
Window {
|
||||
ScrollingWindow {
|
||||
id: root
|
||||
resizable: true
|
||||
width: 600
|
||||
|
|
|
@ -15,14 +15,14 @@ import Qt.labs.settings 1.0
|
|||
|
||||
import "../../styles-uit"
|
||||
import "../../controls-uit" as HifiControls
|
||||
import "../../windows-uit"
|
||||
import "../../windows"
|
||||
|
||||
Window {
|
||||
ScrollingWindow {
|
||||
id: root
|
||||
objectName: "RunningScripts"
|
||||
title: "Running Scripts"
|
||||
resizable: true
|
||||
destroyOnInvisible: true
|
||||
destroyOnHidden: true
|
||||
implicitWidth: 424
|
||||
implicitHeight: isHMD ? 695 : 728
|
||||
minSize: Qt.vector2d(424, 300)
|
||||
|
@ -34,6 +34,9 @@ Window {
|
|||
property var runningScriptsModel: ListModel { }
|
||||
property bool isHMD: false
|
||||
|
||||
onVisibleChanged: console.log("Running scripts visible changed to " + visible)
|
||||
onShownChanged: console.log("Running scripts visible changed to " + visible)
|
||||
|
||||
Settings {
|
||||
category: "Overlay.RunningScripts"
|
||||
property alias x: root.x
|
||||
|
|
|
@ -7,7 +7,7 @@ import "../../windows"
|
|||
import "../../js/Utils.js" as Utils
|
||||
import "../models"
|
||||
|
||||
Window {
|
||||
ScrollingWindow {
|
||||
id: root
|
||||
resizable: true
|
||||
width: 516
|
||||
|
|
|
@ -8,7 +8,7 @@ import "."
|
|||
import ".."
|
||||
import "../../../styles-uit"
|
||||
import "../../../controls-uit" as HifiControls
|
||||
import "../../../windows-uit"
|
||||
import "../../../windows"
|
||||
|
||||
Item {
|
||||
height: column.height + 2 * 8
|
||||
|
|
|
@ -3,7 +3,7 @@ import QtQuick.Controls 1.4
|
|||
|
||||
import "../../../styles-uit"
|
||||
import "../../../controls-uit" as HifiControls
|
||||
import "../../../windows-uit"
|
||||
import "../../../windows"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
|
151
interface/resources/qml/hifi/toolbars/Toolbar.qml
Normal file
151
interface/resources/qml/hifi/toolbars/Toolbar.qml
Normal file
|
@ -0,0 +1,151 @@
|
|||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import Qt.labs.settings 1.0
|
||||
|
||||
import "../../windows"
|
||||
import "."
|
||||
|
||||
Window {
|
||||
id: window
|
||||
frame: ToolFrame {
|
||||
horizontalSpacers: horizontal
|
||||
verticalSpacers: !horizontal
|
||||
}
|
||||
hideBackground: true
|
||||
resizable: false
|
||||
destroyOnCloseButton: false
|
||||
destroyOnHidden: false
|
||||
closable: false
|
||||
shown: true
|
||||
width: content.width
|
||||
height: content.height
|
||||
visible: true
|
||||
// Disable this window from being able to call 'desktop.raise() and desktop.showDesktop'
|
||||
activator: Item {}
|
||||
property bool horizontal: true
|
||||
property real buttonSize: 50;
|
||||
property var buttons: []
|
||||
property var container: horizontal ? row : column
|
||||
|
||||
Settings {
|
||||
category: "toolbar/" + window.objectName
|
||||
property alias x: window.x
|
||||
property alias y: window.y
|
||||
}
|
||||
|
||||
onHorizontalChanged: {
|
||||
var newParent = horizontal ? row : column;
|
||||
for (var i in buttons) {
|
||||
var child = buttons[i];
|
||||
child.parent = newParent;
|
||||
if (horizontal) {
|
||||
child.y = 0
|
||||
} else {
|
||||
child.x = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: content
|
||||
implicitHeight: horizontal ? row.height : column.height
|
||||
implicitWidth: horizontal ? row.width : column.width
|
||||
|
||||
Row {
|
||||
id: row
|
||||
spacing: 6
|
||||
}
|
||||
|
||||
Column {
|
||||
id: column
|
||||
spacing: 6
|
||||
}
|
||||
|
||||
Component { id: toolbarButtonBuilder; ToolbarButton { } }
|
||||
|
||||
Connections {
|
||||
target: desktop
|
||||
onPinnedChanged: {
|
||||
if (!window.pinned) {
|
||||
return;
|
||||
}
|
||||
var newPinned = desktop.pinned;
|
||||
for (var i in buttons) {
|
||||
var child = buttons[i];
|
||||
if (desktop.pinned) {
|
||||
if (!child.pinned) {
|
||||
child.visible = false;
|
||||
}
|
||||
} else {
|
||||
child.visible = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function findButtonIndex(name) {
|
||||
if (!name) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (var i in buttons) {
|
||||
var child = buttons[i];
|
||||
if (child.objectName === name) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
function findButton(name) {
|
||||
var index = findButtonIndex(name);
|
||||
if (index < 0) {
|
||||
return;
|
||||
}
|
||||
return buttons[index];
|
||||
}
|
||||
|
||||
function addButton(properties) {
|
||||
properties = properties || {}
|
||||
|
||||
// If a name is specified, then check if there's an existing button with that name
|
||||
// and return it if so. This will allow multiple clients to listen to a single button,
|
||||
// and allow scripts to be idempotent so they don't duplicate buttons if they're reloaded
|
||||
var result = findButton(properties.objectName);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
properties.toolbar = this;
|
||||
properties.opacity = 0;
|
||||
result = toolbarButtonBuilder.createObject(container, properties);
|
||||
buttons.push(result);
|
||||
result.opacity = 1;
|
||||
updatePinned();
|
||||
return result;
|
||||
}
|
||||
|
||||
function removeButton(name) {
|
||||
var index = findButtonIndex(name);
|
||||
if (index < -1) {
|
||||
console.warn("Tried to remove non-existent button " + name);
|
||||
return;
|
||||
}
|
||||
buttons[index].destroy();
|
||||
buttons.splice(index, 1);
|
||||
updatePinned();
|
||||
}
|
||||
|
||||
function updatePinned() {
|
||||
var newPinned = false;
|
||||
for (var i in buttons) {
|
||||
var child = buttons[i];
|
||||
if (child.pinned) {
|
||||
newPinned = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
pinned = newPinned;
|
||||
}
|
||||
}
|
65
interface/resources/qml/hifi/toolbars/ToolbarButton.qml
Normal file
65
interface/resources/qml/hifi/toolbars/ToolbarButton.qml
Normal file
|
@ -0,0 +1,65 @@
|
|||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
|
||||
Item {
|
||||
id: button
|
||||
property alias imageURL: image.source
|
||||
property alias alpha: button.opacity
|
||||
property var subImage;
|
||||
property int yOffset: 0
|
||||
property int buttonState: 0
|
||||
property var toolbar;
|
||||
property real size: 50 // toolbar ? toolbar.buttonSize : 50
|
||||
width: size; height: size
|
||||
property bool pinned: false
|
||||
clip: true
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: 150
|
||||
easing.type: Easing.InOutCubic
|
||||
}
|
||||
}
|
||||
|
||||
property alias fadeTargetProperty: button.opacity
|
||||
|
||||
onFadeTargetPropertyChanged: {
|
||||
visible = (fadeTargetProperty !== 0.0);
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if ((!visible && fadeTargetProperty != 0.0) || (visible && fadeTargetProperty == 0.0)) {
|
||||
var target = visible;
|
||||
visible = !visible;
|
||||
fadeTargetProperty = target ? 1.0 : 0.0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onButtonStateChanged: {
|
||||
yOffset = size * buttonState
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (subImage) {
|
||||
if (subImage.y) {
|
||||
yOffset = subImage.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
signal clicked()
|
||||
|
||||
Image {
|
||||
id: image
|
||||
y: -button.yOffset;
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: button.clicked();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,119 +0,0 @@
|
|||
//
|
||||
// DefaultFrame.qml
|
||||
//
|
||||
// Created by Bradley Austin Davis on 12 Jan 2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtGraphicalEffects 1.0
|
||||
|
||||
import "."
|
||||
import "../styles-uit"
|
||||
|
||||
Frame {
|
||||
HifiConstants { id: hifi }
|
||||
|
||||
Rectangle {
|
||||
// Dialog frame
|
||||
id: frameContent
|
||||
|
||||
readonly property int iconSize: hifi.dimensions.frameIconSize
|
||||
readonly property int frameMargin: 9
|
||||
readonly property int frameMarginLeft: frameMargin
|
||||
readonly property int frameMarginRight: frameMargin
|
||||
readonly property int frameMarginTop: 2 * frameMargin + iconSize
|
||||
readonly property int frameMarginBottom: iconSize + 11
|
||||
|
||||
anchors {
|
||||
topMargin: -frameMarginTop
|
||||
leftMargin: -frameMarginLeft
|
||||
rightMargin: -frameMarginRight
|
||||
bottomMargin: -frameMarginBottom
|
||||
}
|
||||
anchors.fill: parent
|
||||
color: hifi.colors.baseGrayHighlight40
|
||||
border {
|
||||
width: hifi.dimensions.borderWidth
|
||||
color: hifi.colors.faintGray50
|
||||
}
|
||||
radius: hifi.dimensions.borderRadius
|
||||
|
||||
// Enable dragging of the window
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
drag.target: window
|
||||
}
|
||||
|
||||
Row {
|
||||
id: controlsRow
|
||||
anchors {
|
||||
right: parent.right;
|
||||
top: parent.top;
|
||||
topMargin: frameContent.frameMargin + 1 // Move down a little to visually align with the title
|
||||
rightMargin: frameContent.frameMarginRight;
|
||||
}
|
||||
spacing: frameContent.iconSize / 4
|
||||
|
||||
HiFiGlyphs {
|
||||
// "Pin" button
|
||||
visible: false
|
||||
text: (frame.pinned && !pinClickArea.containsMouse) || (!frame.pinned && pinClickArea.containsMouse) ? hifi.glyphs.pinInverted : hifi.glyphs.pin
|
||||
color: pinClickArea.containsMouse && !pinClickArea.pressed ? hifi.colors.redHighlight : hifi.colors.white
|
||||
size: frameContent.iconSize
|
||||
MouseArea {
|
||||
id: pinClickArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
propagateComposedEvents: true
|
||||
onClicked: { frame.pin(); mouse.accepted = false; }
|
||||
}
|
||||
}
|
||||
|
||||
HiFiGlyphs {
|
||||
// "Close" button
|
||||
visible: window ? window.closable : false
|
||||
text: closeClickArea.containsPress ? hifi.glyphs.closeInverted : hifi.glyphs.close
|
||||
color: closeClickArea.containsMouse ? hifi.colors.redHighlight : hifi.colors.white
|
||||
size: frameContent.iconSize
|
||||
MouseArea {
|
||||
id: closeClickArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: window.visible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RalewayRegular {
|
||||
// Title
|
||||
id: titleText
|
||||
anchors {
|
||||
left: parent.left
|
||||
leftMargin: frameContent.frameMarginLeft + hifi.dimensions.contentMargin.x
|
||||
right: controlsRow.left
|
||||
rightMargin: frameContent.iconSize
|
||||
top: parent.top
|
||||
topMargin: frameContent.frameMargin
|
||||
}
|
||||
text: window ? window.title : ""
|
||||
color: hifi.colors.white
|
||||
size: hifi.fontSizes.overlayTitle
|
||||
}
|
||||
|
||||
DropShadow {
|
||||
source: titleText
|
||||
anchors.fill: titleText
|
||||
horizontalOffset: 2
|
||||
verticalOffset: 2
|
||||
samples: 2
|
||||
color: hifi.colors.baseGrayShadow60
|
||||
visible: (window && window.focus)
|
||||
cached: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
//
|
||||
// Fadable.qml
|
||||
//
|
||||
// Created by Bradley Austin Davis on 15 Jan 2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtGraphicalEffects 1.0
|
||||
|
||||
import "../styles-uit"
|
||||
|
||||
// Enable window visibility transitions
|
||||
FocusScope {
|
||||
id: root
|
||||
HifiConstants { id: hifi }
|
||||
|
||||
Component.onCompleted: {
|
||||
fadeTargetProperty = visible ? 1.0 : 0.0
|
||||
}
|
||||
|
||||
// The target property to animate, usually scale or opacity
|
||||
property alias fadeTargetProperty: root.opacity
|
||||
// always start the property at 0 to enable fade in on creation
|
||||
fadeTargetProperty: 0
|
||||
// DO NOT set visible to false or when derived types override it it
|
||||
// will short circuit the fade in on initial visibility
|
||||
// visible: false <--- NO
|
||||
|
||||
// Some dialogs should be destroyed when they become
|
||||
// invisible, so handle that
|
||||
onVisibleChanged: {
|
||||
// If someone directly set the visibility to false
|
||||
// toggle it back on and use the targetVisible flag to transition
|
||||
// via fading.
|
||||
if ((!visible && fadeTargetProperty != 0.0) || (visible && fadeTargetProperty == 0.0)) {
|
||||
var target = visible;
|
||||
visible = !visible;
|
||||
fadeTargetProperty = target ? 1.0 : 0.0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// The actual animator
|
||||
Behavior on fadeTargetProperty {
|
||||
NumberAnimation {
|
||||
duration: hifi.effects.fadeInDuration
|
||||
easing.type: Easing.InOutCubic
|
||||
}
|
||||
}
|
||||
|
||||
// Once we're transparent, disable the dialog's visibility
|
||||
onFadeTargetPropertyChanged: {
|
||||
visible = (fadeTargetProperty != 0.0);
|
||||
}
|
||||
}
|
|
@ -1,133 +0,0 @@
|
|||
//
|
||||
// Frame.qml
|
||||
//
|
||||
// Created by Bradley Austin Davis on 12 Jan 2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtGraphicalEffects 1.0
|
||||
|
||||
import "../styles-uit"
|
||||
import "../js/Utils.js" as Utils
|
||||
|
||||
Item {
|
||||
id: frame
|
||||
HifiConstants { id: hifi }
|
||||
|
||||
default property var decoration
|
||||
|
||||
property bool gradientsSupported: desktop.gradientsSupported
|
||||
|
||||
readonly property int frameMarginLeft: frameContent.frameMarginLeft
|
||||
readonly property int frameMarginRight: frameContent.frameMarginRight
|
||||
readonly property int frameMarginTop: frameContent.frameMarginTop
|
||||
readonly property int frameMarginBottom: frameContent.frameMarginBottom
|
||||
|
||||
// Frames always fill their parents, but their decorations may extend
|
||||
// beyond the window via negative margin sizes
|
||||
anchors.fill: parent
|
||||
|
||||
children: [
|
||||
focusShadow,
|
||||
decoration,
|
||||
sizeOutline,
|
||||
debugZ,
|
||||
sizeDrag
|
||||
]
|
||||
|
||||
Text {
|
||||
id: debugZ
|
||||
visible: DebugQML
|
||||
text: window ? "Z: " + window.z : ""
|
||||
y: window ? window.height + 4 : 0
|
||||
}
|
||||
|
||||
function deltaSize(dx, dy) {
|
||||
var newSize = Qt.vector2d(window.width + dx, window.height + dy);
|
||||
newSize = Utils.clampVector(newSize, window.minSize, window.maxSize);
|
||||
window.width = newSize.x
|
||||
window.height = newSize.y
|
||||
}
|
||||
|
||||
RadialGradient {
|
||||
id: focusShadow
|
||||
width: 1.66 * window.width
|
||||
height: 1.66 * window.height
|
||||
x: (window.width - width) / 2
|
||||
y: window.height / 2 - 0.375 * height
|
||||
visible: gradientsSupported && window && window.focus && pane.visible
|
||||
gradient: Gradient {
|
||||
// GradientStop position 0.5 is at full circumference of circle that fits inside the square.
|
||||
GradientStop { position: 0.0; color: "#ff000000" } // black, 100% opacity
|
||||
GradientStop { position: 0.333; color: "#1f000000" } // black, 12% opacity
|
||||
GradientStop { position: 0.5; color: "#00000000" } // black, 0% opacity
|
||||
GradientStop { position: 1.0; color: "#00000000" }
|
||||
}
|
||||
cached: true
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: sizeOutline
|
||||
x: -frameMarginLeft
|
||||
y: -frameMarginTop
|
||||
width: window ? window.width + frameMarginLeft + frameMarginRight + 2 : 0
|
||||
height: window ? window.height + frameMarginTop + frameMarginBottom + 2 : 0
|
||||
color: hifi.colors.baseGrayHighlight15
|
||||
border.width: 3
|
||||
border.color: hifi.colors.white50
|
||||
radius: hifi.dimensions.borderRadius
|
||||
visible: window ? !pane.visible : false
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
// Resize handle
|
||||
id: sizeDrag
|
||||
width: hifi.dimensions.frameIconSize
|
||||
height: hifi.dimensions.frameIconSize
|
||||
enabled: window ? window.resizable : false
|
||||
hoverEnabled: true
|
||||
x: window ? window.width + frameMarginRight - hifi.dimensions.frameIconSize : 0
|
||||
y: window ? window.height + 4 : 0
|
||||
property vector2d pressOrigin
|
||||
property vector2d sizeOrigin
|
||||
property bool hid: false
|
||||
onPressed: {
|
||||
//console.log("Pressed on size")
|
||||
pressOrigin = Qt.vector2d(mouseX, mouseY)
|
||||
sizeOrigin = Qt.vector2d(window.content.width, window.content.height)
|
||||
hid = false;
|
||||
}
|
||||
onReleased: {
|
||||
if (hid) {
|
||||
pane.visible = true
|
||||
frameContent.visible = true
|
||||
hid = false;
|
||||
}
|
||||
}
|
||||
onPositionChanged: {
|
||||
if (pressed) {
|
||||
if (pane.visible) {
|
||||
pane.visible = false;
|
||||
frameContent.visible = false
|
||||
hid = true;
|
||||
}
|
||||
var delta = Qt.vector2d(mouseX, mouseY).minus(pressOrigin);
|
||||
frame.deltaSize(delta.x, delta.y)
|
||||
}
|
||||
}
|
||||
HiFiGlyphs {
|
||||
visible: sizeDrag.enabled
|
||||
x: -11 // Move a little to visually align
|
||||
y: window.modality == Qt.ApplicationModal ? -6 : -4
|
||||
text: hifi.glyphs.resizeHandle
|
||||
size: hifi.dimensions.frameIconSize + 10
|
||||
color: sizeDrag.containsMouse || sizeDrag.pressed
|
||||
? hifi.colors.white
|
||||
: (window.colorScheme == hifi.colorSchemes.dark ? hifi.colors.white50 : hifi.colors.lightGrayText80)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,98 +0,0 @@
|
|||
//
|
||||
// ModalFrame.qml
|
||||
//
|
||||
// Created by Bradley Austin Davis on 15 Jan 2016
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
|
||||
import "."
|
||||
import "../controls-uit"
|
||||
import "../styles-uit"
|
||||
|
||||
Frame {
|
||||
HifiConstants { id: hifi }
|
||||
|
||||
Rectangle {
|
||||
id: frameContent
|
||||
|
||||
readonly property bool hasTitle: window.title != ""
|
||||
|
||||
readonly property int frameMarginLeft: hifi.dimensions.modalDialogMargin.x
|
||||
readonly property int frameMarginRight: hifi.dimensions.modalDialogMargin.x
|
||||
readonly property int frameMarginTop: hifi.dimensions.modalDialogMargin.y + (frameContent.hasTitle ? hifi.dimensions.modalDialogTitleHeight + 10 : 0)
|
||||
readonly property int frameMarginBottom: hifi.dimensions.modalDialogMargin.y
|
||||
|
||||
signal frameClicked();
|
||||
|
||||
anchors {
|
||||
fill: parent
|
||||
topMargin: -frameMarginTop
|
||||
leftMargin: -frameMarginLeft
|
||||
rightMargin: -frameMarginRight
|
||||
bottomMargin: -frameMarginBottom
|
||||
}
|
||||
|
||||
border {
|
||||
width: hifi.dimensions.borderWidth
|
||||
color: hifi.colors.lightGrayText80
|
||||
}
|
||||
radius: hifi.dimensions.borderRadius
|
||||
color: hifi.colors.faintGray
|
||||
|
||||
// Enable dragging of the window
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
drag.target: window
|
||||
enabled: window.draggable
|
||||
onClicked: window.frameClicked();
|
||||
}
|
||||
|
||||
Item {
|
||||
visible: frameContent.hasTitle
|
||||
anchors.fill: parent
|
||||
anchors {
|
||||
topMargin: -parent.anchors.topMargin
|
||||
leftMargin: -parent.anchors.leftMargin
|
||||
rightMargin: -parent.anchors.rightMargin
|
||||
}
|
||||
|
||||
Item {
|
||||
width: title.width + (icon.text !== "" ? icon.width + hifi.dimensions.contentSpacing.x : 0)
|
||||
x: (parent.width - width) / 2
|
||||
|
||||
onWidthChanged: window.titleWidth = width
|
||||
|
||||
HiFiGlyphs {
|
||||
id: icon
|
||||
text: window.iconText ? window.iconText : ""
|
||||
size: window.iconSize ? window.iconSize : 30
|
||||
color: hifi.colors.lightGray
|
||||
visible: text != ""
|
||||
anchors.verticalCenter: title.verticalCenter
|
||||
anchors.left: parent.left
|
||||
}
|
||||
RalewayRegular {
|
||||
id: title
|
||||
text: window.title
|
||||
elide: Text.ElideRight
|
||||
color: hifi.colors.baseGrayHighlight
|
||||
size: hifi.fontSizes.overlayTitle
|
||||
y: -hifi.dimensions.modalDialogTitleHeight
|
||||
anchors.right: parent.right
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: 1
|
||||
color: hifi.colors.lightGray
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
//
|
||||
// ModalWindow.qml
|
||||
//
|
||||
// Created by Bradley Austin Davis on 22 Jan 2016
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
|
||||
import "."
|
||||
|
||||
Window {
|
||||
id: window
|
||||
modality: Qt.ApplicationModal
|
||||
destroyOnCloseButton: true
|
||||
destroyOnInvisible: true
|
||||
frame: ModalFrame { }
|
||||
|
||||
property int colorScheme: hifi.colorSchemes.light
|
||||
property bool draggable: false
|
||||
|
||||
signal frameClicked();
|
||||
|
||||
anchors.centerIn: draggable ? undefined : parent
|
||||
}
|
|
@ -1,343 +0,0 @@
|
|||
//
|
||||
// Window.qml
|
||||
//
|
||||
// Created by Bradley Austin Davis on 12 Jan 2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
import QtGraphicalEffects 1.0
|
||||
|
||||
import "."
|
||||
import "../styles-uit"
|
||||
|
||||
// FIXME how do I set the initial position of a window without
|
||||
// overriding places where the a individual client of the window
|
||||
// might be setting the position with a Settings{} element?
|
||||
|
||||
// FIXME how to I enable dragging without allowing the window to lay outside
|
||||
// of the desktop? How do I ensure when the desktop resizes all the windows
|
||||
// are still at least partially visible?
|
||||
Fadable {
|
||||
id: window
|
||||
HifiConstants { id: hifi }
|
||||
|
||||
// The Window size is the size of the content, while the frame
|
||||
// decorations can extend outside it.
|
||||
implicitHeight: content ? content.height : 0
|
||||
implicitWidth: content ? content.width : 0
|
||||
x: desktop.invalid_position; y: desktop.invalid_position;
|
||||
enabled: visible
|
||||
|
||||
signal windowDestroyed();
|
||||
|
||||
property int modality: Qt.NonModal
|
||||
readonly property bool topLevelWindow: true
|
||||
property string title
|
||||
// Should the window be closable control?
|
||||
property bool closable: true
|
||||
// Should the window try to remain on top of other windows?
|
||||
property bool alwaysOnTop: false
|
||||
// Should hitting the close button hide or destroy the window?
|
||||
property bool destroyOnCloseButton: true
|
||||
// Should hiding the window destroy it or just hide it?
|
||||
property bool destroyOnInvisible: false
|
||||
// FIXME support for pinned / unpinned pending full design
|
||||
// property bool pinnable: false
|
||||
// property bool pinned: false
|
||||
property bool resizable: false
|
||||
property bool gradientsSupported: desktop.gradientsSupported
|
||||
property int colorScheme: hifi.colorSchemes.dark
|
||||
|
||||
property vector2d minSize: Qt.vector2d(100, 100)
|
||||
property vector2d maxSize: Qt.vector2d(1280, 800)
|
||||
|
||||
// The content to place inside the window, determined by the client
|
||||
default property var content
|
||||
|
||||
property var footer: Item { } // Optional static footer at the bottom of the dialog.
|
||||
|
||||
function setDefaultFocus() {} // Default function; can be overridden by dialogs.
|
||||
|
||||
property var rectifier: Timer {
|
||||
property bool executing: false;
|
||||
interval: 100
|
||||
repeat: false
|
||||
running: false
|
||||
|
||||
onTriggered: {
|
||||
executing = true;
|
||||
x = Math.floor(x);
|
||||
y = Math.floor(y);
|
||||
executing = false;
|
||||
}
|
||||
|
||||
function begin() {
|
||||
if (!executing) {
|
||||
restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
onXChanged: rectifier.begin();
|
||||
onYChanged: rectifier.begin();
|
||||
|
||||
// This mouse area serves to raise the window. To function, it must live
|
||||
// in the window and have a higher Z-order than the content, but follow
|
||||
// the position and size of frame decoration
|
||||
property var activator: MouseArea {
|
||||
width: frame.decoration.width
|
||||
height: frame.decoration.height
|
||||
x: frame.decoration.anchors.leftMargin
|
||||
y: frame.decoration.anchors.topMargin
|
||||
propagateComposedEvents: true
|
||||
acceptedButtons: Qt.AllButtons
|
||||
enabled: window.visible
|
||||
onPressed: {
|
||||
//console.log("Pressed on activator area");
|
||||
window.raise();
|
||||
mouse.accepted = false;
|
||||
}
|
||||
}
|
||||
|
||||
// This mouse area serves to swallow mouse events while the mouse is over the window
|
||||
// to prevent things like mouse wheel events from reaching the application and changing
|
||||
// the camera if the user is scrolling through a list and gets to the end.
|
||||
property var swallower: MouseArea {
|
||||
width: frame.decoration.width
|
||||
height: frame.decoration.height
|
||||
x: frame.decoration.anchors.leftMargin
|
||||
y: frame.decoration.anchors.topMargin
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.AllButtons
|
||||
enabled: window.visible
|
||||
onClicked: {}
|
||||
onDoubleClicked: {}
|
||||
onPressAndHold: {}
|
||||
onReleased: {}
|
||||
onWheel: {}
|
||||
}
|
||||
|
||||
// Default to a standard frame. Can be overriden to provide custom
|
||||
// frame styles, like a full desktop frame to simulate a modal window
|
||||
property var frame: DefaultFrame { }
|
||||
|
||||
// Scrollable window content.
|
||||
property var pane: Item {
|
||||
property bool isScrolling: scrollView.height < scrollView.contentItem.height
|
||||
property int contentWidth: scrollView.width - (isScrolling ? 10 : 0)
|
||||
property int scrollHeight: scrollView.height
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.rightMargin: isScrolling ? 11 : 0
|
||||
|
||||
Rectangle {
|
||||
id: contentBackground
|
||||
anchors.fill: parent
|
||||
anchors.rightMargin: parent.isScrolling ? 11 : 0
|
||||
color: hifi.colors.baseGray
|
||||
visible: modality != Qt.ApplicationModal
|
||||
}
|
||||
|
||||
LinearGradient {
|
||||
visible: gradientsSupported && modality != Qt.ApplicationModal
|
||||
anchors.top: contentBackground.bottom
|
||||
anchors.left: contentBackground.left
|
||||
width: contentBackground.width - 1
|
||||
height: 4
|
||||
start: Qt.point(0, 0)
|
||||
end: Qt.point(0, 4)
|
||||
gradient: Gradient {
|
||||
GradientStop { position: 0.0; color: hifi.colors.darkGray }
|
||||
GradientStop { position: 1.0; color: hifi.colors.darkGray0 }
|
||||
}
|
||||
cached: true
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
id: scrollView
|
||||
contentItem: content
|
||||
horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff
|
||||
verticalScrollBarPolicy: Qt.ScrollBarAsNeeded
|
||||
anchors.fill: parent
|
||||
anchors.rightMargin: parent.isScrolling ? 1 : 0
|
||||
anchors.bottomMargin: footer.height > 0 ? footerPane.height : 0
|
||||
|
||||
style: ScrollViewStyle {
|
||||
|
||||
padding.right: -7 // Move to right away from content.
|
||||
|
||||
handle: Item {
|
||||
implicitWidth: 8
|
||||
Rectangle {
|
||||
radius: 4
|
||||
color: hifi.colors.white30
|
||||
anchors {
|
||||
fill: parent
|
||||
leftMargin: 2 // Finesse size and position.
|
||||
topMargin: 1
|
||||
bottomMargin: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scrollBarBackground: Item {
|
||||
implicitWidth: 10
|
||||
Rectangle {
|
||||
color: hifi.colors.darkGray30
|
||||
radius: 4
|
||||
anchors {
|
||||
fill: parent
|
||||
topMargin: -1 // Finesse size
|
||||
bottomMargin: -2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
incrementControl: Item {
|
||||
visible: false
|
||||
}
|
||||
|
||||
decrementControl: Item {
|
||||
visible: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
// Optional non-scrolling footer.
|
||||
id: footerPane
|
||||
anchors {
|
||||
left: parent.left
|
||||
bottom: parent.bottom
|
||||
}
|
||||
width: parent.contentWidth
|
||||
height: footer.height + 2 * hifi.dimensions.contentSpacing.y + 3
|
||||
color: hifi.colors.baseGray
|
||||
visible: footer.height > 0
|
||||
|
||||
Item {
|
||||
// Horizontal rule.
|
||||
anchors.fill: parent
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
y: 1 // Stop displaying content just above horizontal rule/=.
|
||||
color: hifi.colors.baseGrayShadow
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
y: 2
|
||||
color: hifi.colors.baseGrayHighlight
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: 3 // Horizontal rule.
|
||||
children: [ footer ]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
children: [ swallower, frame, pane, activator ]
|
||||
|
||||
Component.onCompleted: {
|
||||
window.parentChanged.connect(raise);
|
||||
raise();
|
||||
setDefaultFocus();
|
||||
centerOrReposition();
|
||||
}
|
||||
Component.onDestruction: {
|
||||
window.parentChanged.disconnect(raise); // Prevent warning on shutdown
|
||||
windowDestroyed();
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if (!visible && destroyOnInvisible) {
|
||||
destroy();
|
||||
return;
|
||||
}
|
||||
if (visible) {
|
||||
raise();
|
||||
}
|
||||
enabled = visible
|
||||
|
||||
if (visible && parent) {
|
||||
centerOrReposition();
|
||||
}
|
||||
}
|
||||
|
||||
function centerOrReposition() {
|
||||
if (x == desktop.invalid_position && y == desktop.invalid_position) {
|
||||
desktop.centerOnVisible(window);
|
||||
} else {
|
||||
desktop.repositionOnVisible(window);
|
||||
}
|
||||
}
|
||||
|
||||
function raise() {
|
||||
if (visible && parent) {
|
||||
desktop.raise(window)
|
||||
}
|
||||
}
|
||||
|
||||
function pin() {
|
||||
// pinned = ! pinned
|
||||
}
|
||||
|
||||
// our close function performs the same way as the OffscreenUI class:
|
||||
// don't do anything but manipulate the targetVisible flag and let the other
|
||||
// mechanisms decide if the window should be destroyed after the close
|
||||
// animation completes
|
||||
// FIXME using this close function messes up the visibility signals received by the
|
||||
// type and it's derived types
|
||||
// function close() {
|
||||
// console.log("Closing " + window)
|
||||
// if (destroyOnCloseButton) {
|
||||
// destroyOnInvisible = true
|
||||
// }
|
||||
// visible = false;
|
||||
// }
|
||||
|
||||
function framedRect() {
|
||||
if (!frame || !frame.decoration) {
|
||||
return Qt.rect(0, 0, window.width, window.height)
|
||||
}
|
||||
return Qt.rect(frame.decoration.anchors.leftMargin, frame.decoration.anchors.topMargin,
|
||||
window.width - frame.decoration.anchors.leftMargin - frame.decoration.anchors.rightMargin,
|
||||
window.height - frame.decoration.anchors.topMargin - frame.decoration.anchors.bottomMargin)
|
||||
}
|
||||
|
||||
Keys.onPressed: {
|
||||
switch(event.key) {
|
||||
case Qt.Key_Control:
|
||||
case Qt.Key_Shift:
|
||||
case Qt.Key_Meta:
|
||||
case Qt.Key_Alt:
|
||||
break;
|
||||
|
||||
case Qt.Key_W:
|
||||
if (window.closable && (event.modifiers === Qt.ControlModifier)) {
|
||||
visible = false
|
||||
event.accepted = true
|
||||
}
|
||||
// fall through
|
||||
|
||||
default:
|
||||
// Consume unmodified keyboard entries while the window is focused, to prevent them
|
||||
// from propagating to the application
|
||||
if (event.modifiers === Qt.NoModifier) {
|
||||
event.accepted = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,20 +1,48 @@
|
|||
//
|
||||
// DefaultFrame.qml
|
||||
//
|
||||
// Created by Bradley Austin Davis on 12 Jan 2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtGraphicalEffects 1.0
|
||||
|
||||
import "."
|
||||
import "../controls"
|
||||
import "../styles-uit"
|
||||
|
||||
Frame {
|
||||
id: frame
|
||||
|
||||
property bool wideTopMargin: (window && (window.closable || window.title));
|
||||
HifiConstants { id: hifi }
|
||||
|
||||
Rectangle {
|
||||
anchors { margins: -iconSize; topMargin: -iconSize * (wideTopMargin ? 2 : 1); }
|
||||
anchors.fill: parent;
|
||||
color: "#7f7f7f7f";
|
||||
radius: 3;
|
||||
// Dialog frame
|
||||
id: frameContent
|
||||
|
||||
// Allow dragging of the window
|
||||
readonly property int iconSize: hifi.dimensions.frameIconSize
|
||||
readonly property int frameMargin: 9
|
||||
readonly property int frameMarginLeft: frameMargin
|
||||
readonly property int frameMarginRight: frameMargin
|
||||
readonly property int frameMarginTop: 2 * frameMargin + iconSize
|
||||
readonly property int frameMarginBottom: iconSize + 11
|
||||
|
||||
anchors {
|
||||
topMargin: -frameMarginTop
|
||||
leftMargin: -frameMarginLeft
|
||||
rightMargin: -frameMarginRight
|
||||
bottomMargin: -frameMarginBottom
|
||||
}
|
||||
anchors.fill: parent
|
||||
color: hifi.colors.baseGrayHighlight40
|
||||
border {
|
||||
width: hifi.dimensions.borderWidth
|
||||
color: hifi.colors.faintGray50
|
||||
}
|
||||
radius: hifi.dimensions.borderRadius
|
||||
|
||||
// Enable dragging of the window
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
drag.target: window
|
||||
|
@ -22,48 +50,70 @@ Frame {
|
|||
|
||||
Row {
|
||||
id: controlsRow
|
||||
anchors { right: parent.right; top: parent.top; rightMargin: iconSize; topMargin: iconSize / 2; }
|
||||
spacing: iconSize / 4
|
||||
FontAwesome {
|
||||
visible: false
|
||||
text: "\uf08d"
|
||||
style: Text.Outline; styleColor: "white"
|
||||
size: frame.iconSize
|
||||
rotation: !frame.parent ? 90 : frame.parent.pinned ? 0 : 90
|
||||
color: frame.pinned ? "red" : "black"
|
||||
anchors {
|
||||
right: parent.right;
|
||||
top: parent.top;
|
||||
topMargin: frameContent.frameMargin + 1 // Move down a little to visually align with the title
|
||||
rightMargin: frameContent.frameMarginRight;
|
||||
}
|
||||
spacing: frameContent.iconSize / 4
|
||||
|
||||
HiFiGlyphs {
|
||||
// "Pin" button
|
||||
visible: window.pinnable
|
||||
text: window.pinned ? hifi.glyphs.pinInverted : hifi.glyphs.pin
|
||||
color: pinClickArea.pressed ? hifi.colors.redHighlight : hifi.colors.white
|
||||
size: frameContent.iconSize
|
||||
MouseArea {
|
||||
id: pinClickArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
propagateComposedEvents: true
|
||||
onClicked: { frame.pin(); mouse.accepted = false; }
|
||||
onClicked: window.pinned = !window.pinned;
|
||||
}
|
||||
}
|
||||
FontAwesome {
|
||||
|
||||
HiFiGlyphs {
|
||||
// "Close" button
|
||||
visible: window ? window.closable : false
|
||||
text: closeClickArea.containsMouse ? "\uf057" : "\uf05c"
|
||||
style: Text.Outline;
|
||||
styleColor: "white"
|
||||
color: closeClickArea.containsMouse ? "red" : "black"
|
||||
size: frame.iconSize
|
||||
text: closeClickArea.containsPress ? hifi.glyphs.closeInverted : hifi.glyphs.close
|
||||
color: closeClickArea.containsMouse ? hifi.colors.redHighlight : hifi.colors.white
|
||||
size: frameContent.iconSize
|
||||
MouseArea {
|
||||
id: closeClickArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: window.visible = false;
|
||||
onClicked: window.shown = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
RalewayRegular {
|
||||
// Title
|
||||
id: titleText
|
||||
anchors { left: parent.left; leftMargin: iconSize; right: controlsRow.left; rightMargin: iconSize; top: parent.top; topMargin: iconSize / 2; }
|
||||
anchors {
|
||||
left: parent.left
|
||||
leftMargin: frameContent.frameMarginLeft + hifi.dimensions.contentMargin.x
|
||||
right: controlsRow.left
|
||||
rightMargin: frameContent.iconSize
|
||||
top: parent.top
|
||||
topMargin: frameContent.frameMargin
|
||||
}
|
||||
text: window ? window.title : ""
|
||||
elide: Text.ElideRight
|
||||
font.bold: true
|
||||
color: (window && window.focus) ? "white" : "gray"
|
||||
style: Text.Outline;
|
||||
styleColor: "black"
|
||||
color: hifi.colors.white
|
||||
size: hifi.fontSizes.overlayTitle
|
||||
}
|
||||
|
||||
DropShadow {
|
||||
source: titleText
|
||||
anchors.fill: titleText
|
||||
horizontalOffset: 2
|
||||
verticalOffset: 2
|
||||
samples: 2
|
||||
color: hifi.colors.baseGrayShadow60
|
||||
visible: (window && window.focus)
|
||||
cached: true
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,18 @@
|
|||
//
|
||||
// Fadable.qml
|
||||
//
|
||||
// Created by Bradley Austin Davis on 15 Jan 2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtGraphicalEffects 1.0
|
||||
import "."
|
||||
import "../styles"
|
||||
|
||||
import "../styles-uit"
|
||||
|
||||
// Enable window visibility transitions
|
||||
FocusScope {
|
||||
|
@ -13,6 +23,7 @@ FocusScope {
|
|||
fadeTargetProperty = visible ? 1.0 : 0.0
|
||||
}
|
||||
|
||||
property var completionCallback;
|
||||
// The target property to animate, usually scale or opacity
|
||||
property alias fadeTargetProperty: root.opacity
|
||||
// always start the property at 0 to enable fade in on creation
|
||||
|
@ -33,6 +44,13 @@ FocusScope {
|
|||
fadeTargetProperty = target ? 1.0 : 0.0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Now handle completions
|
||||
if (completionCallback) {
|
||||
completionCallback();
|
||||
completionCallback = undefined;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// The actual animator
|
||||
|
@ -43,8 +61,17 @@ FocusScope {
|
|||
}
|
||||
}
|
||||
|
||||
// Once we're transparent, disable the dialog's visibility
|
||||
onFadeTargetPropertyChanged: {
|
||||
visible = (fadeTargetProperty != 0.0);
|
||||
}
|
||||
|
||||
function fadeIn(callback) {
|
||||
completionCallback = callback;
|
||||
fadeTargetProperty = 1.0;
|
||||
}
|
||||
|
||||
function fadeOut(callback) {
|
||||
completionCallback = callback;
|
||||
fadeTargetProperty = 0.0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,24 +1,42 @@
|
|||
import QtQuick 2.5
|
||||
//
|
||||
// Frame.qml
|
||||
//
|
||||
// Created by Bradley Austin Davis on 12 Jan 2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import "../controls"
|
||||
import QtQuick 2.5
|
||||
import QtGraphicalEffects 1.0
|
||||
|
||||
import "../styles-uit"
|
||||
import "../js/Utils.js" as Utils
|
||||
|
||||
Item {
|
||||
id: frame
|
||||
HifiConstants { id: hifi }
|
||||
|
||||
default property var decoration
|
||||
|
||||
property bool gradientsSupported: desktop.gradientsSupported
|
||||
|
||||
readonly property int frameMarginLeft: frameContent.frameMarginLeft
|
||||
readonly property int frameMarginRight: frameContent.frameMarginRight
|
||||
readonly property int frameMarginTop: frameContent.frameMarginTop
|
||||
readonly property int frameMarginBottom: frameContent.frameMarginBottom
|
||||
|
||||
// Frames always fill their parents, but their decorations may extend
|
||||
// beyond the window via negative margin sizes
|
||||
anchors.fill: parent
|
||||
|
||||
// Convenience accessor for the window
|
||||
property alias window: frame.parent
|
||||
readonly property int iconSize: 24
|
||||
default property var decoration;
|
||||
|
||||
children: [
|
||||
focusShadow,
|
||||
decoration,
|
||||
sizeOutline,
|
||||
debugZ,
|
||||
sizeDrag,
|
||||
sizeDrag
|
||||
]
|
||||
|
||||
Text {
|
||||
|
@ -35,57 +53,81 @@ Item {
|
|||
window.height = newSize.y
|
||||
}
|
||||
|
||||
RadialGradient {
|
||||
id: focusShadow
|
||||
width: 1.66 * window.width
|
||||
height: 1.66 * window.height
|
||||
x: (window.width - width) / 2
|
||||
y: window.height / 2 - 0.375 * height
|
||||
visible: gradientsSupported && window && window.focus && window.content.visible
|
||||
gradient: Gradient {
|
||||
// GradientStop position 0.5 is at full circumference of circle that fits inside the square.
|
||||
GradientStop { position: 0.0; color: "#ff000000" } // black, 100% opacity
|
||||
GradientStop { position: 0.333; color: "#1f000000" } // black, 12% opacity
|
||||
GradientStop { position: 0.5; color: "#00000000" } // black, 0% opacity
|
||||
GradientStop { position: 1.0; color: "#00000000" }
|
||||
}
|
||||
cached: true
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: sizeOutline
|
||||
width: window ? window.width : 0
|
||||
height: window ? window.height : 0
|
||||
color: "#00000000"
|
||||
border.width: 4
|
||||
radius: 10
|
||||
x: -frameMarginLeft
|
||||
y: -frameMarginTop
|
||||
width: window ? window.width + frameMarginLeft + frameMarginRight + 2 : 0
|
||||
height: window ? window.height + frameMarginTop + frameMarginBottom + 2 : 0
|
||||
color: hifi.colors.baseGrayHighlight15
|
||||
border.width: 3
|
||||
border.color: hifi.colors.white50
|
||||
radius: hifi.dimensions.borderRadius
|
||||
visible: window ? !window.content.visible : false
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
// Resize handle
|
||||
id: sizeDrag
|
||||
width: iconSize
|
||||
height: iconSize
|
||||
width: hifi.dimensions.frameIconSize
|
||||
height: hifi.dimensions.frameIconSize
|
||||
enabled: window ? window.resizable : false
|
||||
x: window ? window.width : 0
|
||||
y: window ? window.height : 0
|
||||
hoverEnabled: true
|
||||
x: window ? window.width + frameMarginRight - hifi.dimensions.frameIconSize : 0
|
||||
y: window ? window.height + 4 : 0
|
||||
property vector2d pressOrigin
|
||||
property vector2d sizeOrigin
|
||||
property bool hid: false
|
||||
onPressed: {
|
||||
console.log("Pressed on size")
|
||||
//console.log("Pressed on size")
|
||||
pressOrigin = Qt.vector2d(mouseX, mouseY)
|
||||
sizeOrigin = Qt.vector2d(window.content.width, window.content.height)
|
||||
hid = false;
|
||||
}
|
||||
onReleased: {
|
||||
if (hid) {
|
||||
window.content.visible = true
|
||||
pane.visible = true
|
||||
frameContent.visible = true
|
||||
hid = false;
|
||||
}
|
||||
}
|
||||
onPositionChanged: {
|
||||
if (pressed) {
|
||||
if (window.content.visible) {
|
||||
window.content.visible = false;
|
||||
if (pane.visible) {
|
||||
pane.visible = false;
|
||||
frameContent.visible = false
|
||||
hid = true;
|
||||
}
|
||||
var delta = Qt.vector2d(mouseX, mouseY).minus(pressOrigin);
|
||||
frame.deltaSize(delta.x, delta.y)
|
||||
}
|
||||
}
|
||||
FontAwesome {
|
||||
HiFiGlyphs {
|
||||
visible: sizeDrag.enabled
|
||||
rotation: -45
|
||||
anchors { centerIn: parent }
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: "\uf07d"
|
||||
size: iconSize / 3 * 2
|
||||
style: Text.Outline; styleColor: "white"
|
||||
x: -11 // Move a little to visually align
|
||||
y: window.modality == Qt.ApplicationModal ? -6 : -4
|
||||
text: hifi.glyphs.resizeHandle
|
||||
size: hifi.dimensions.frameIconSize + 10
|
||||
color: sizeDrag.containsMouse || sizeDrag.pressed
|
||||
? hifi.colors.white
|
||||
: (window.colorScheme == hifi.colorSchemes.dark ? hifi.colors.white50 : hifi.colors.lightGrayText80)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import QtQuick 2.5
|
|||
|
||||
import "."
|
||||
|
||||
Frame {
|
||||
Item {
|
||||
id: frame
|
||||
|
||||
Item { anchors.fill: parent }
|
||||
|
|
|
@ -1,36 +1,98 @@
|
|||
//
|
||||
// ModalFrame.qml
|
||||
//
|
||||
// Created by Bradley Austin Davis on 15 Jan 2016
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
|
||||
import "."
|
||||
import "../controls"
|
||||
import "../controls-uit"
|
||||
import "../styles-uit"
|
||||
|
||||
Frame {
|
||||
id: frame
|
||||
HifiConstants { id: hifi }
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
Rectangle {
|
||||
id: frameContent
|
||||
|
||||
Rectangle {
|
||||
id: background
|
||||
anchors.fill: parent
|
||||
anchors.margins: -4096
|
||||
visible: window.visible
|
||||
color: "#7f7f7f7f";
|
||||
radius: 3;
|
||||
readonly property bool hasTitle: window.title != ""
|
||||
|
||||
readonly property int frameMarginLeft: hifi.dimensions.modalDialogMargin.x
|
||||
readonly property int frameMarginRight: hifi.dimensions.modalDialogMargin.x
|
||||
readonly property int frameMarginTop: hifi.dimensions.modalDialogMargin.y + (frameContent.hasTitle ? hifi.dimensions.modalDialogTitleHeight + 10 : 0)
|
||||
readonly property int frameMarginBottom: hifi.dimensions.modalDialogMargin.y
|
||||
|
||||
signal frameClicked();
|
||||
|
||||
anchors {
|
||||
fill: parent
|
||||
topMargin: -frameMarginTop
|
||||
leftMargin: -frameMarginLeft
|
||||
rightMargin: -frameMarginRight
|
||||
bottomMargin: -frameMarginBottom
|
||||
}
|
||||
|
||||
Text {
|
||||
y: -implicitHeight - iconSize / 2
|
||||
text: window.title
|
||||
elide: Text.ElideRight
|
||||
font.bold: true
|
||||
color: window.focus ? "white" : "gray"
|
||||
style: Text.Outline;
|
||||
styleColor: "black"
|
||||
border {
|
||||
width: hifi.dimensions.borderWidth
|
||||
color: hifi.colors.lightGrayText80
|
||||
}
|
||||
radius: hifi.dimensions.borderRadius
|
||||
color: hifi.colors.faintGray
|
||||
|
||||
// Enable dragging of the window
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
drag.target: window
|
||||
enabled: window.draggable
|
||||
onClicked: window.frameClicked();
|
||||
}
|
||||
|
||||
Item {
|
||||
visible: frameContent.hasTitle
|
||||
anchors.fill: parent
|
||||
anchors {
|
||||
topMargin: -parent.anchors.topMargin
|
||||
leftMargin: -parent.anchors.leftMargin
|
||||
rightMargin: -parent.anchors.rightMargin
|
||||
}
|
||||
|
||||
Item {
|
||||
width: title.width + (icon.text !== "" ? icon.width + hifi.dimensions.contentSpacing.x : 0)
|
||||
x: (parent.width - width) / 2
|
||||
|
||||
onWidthChanged: window.titleWidth = width
|
||||
|
||||
HiFiGlyphs {
|
||||
id: icon
|
||||
text: window.iconText ? window.iconText : ""
|
||||
size: window.iconSize ? window.iconSize : 30
|
||||
color: hifi.colors.lightGray
|
||||
visible: text != ""
|
||||
anchors.verticalCenter: title.verticalCenter
|
||||
anchors.left: parent.left
|
||||
}
|
||||
RalewayRegular {
|
||||
id: title
|
||||
text: window.title
|
||||
elide: Text.ElideRight
|
||||
color: hifi.colors.baseGrayHighlight
|
||||
size: hifi.fontSizes.overlayTitle
|
||||
y: -hifi.dimensions.modalDialogTitleHeight
|
||||
anchors.right: parent.right
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: 1
|
||||
color: hifi.colors.lightGray
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -1,14 +1,28 @@
|
|||
//
|
||||
// ModalWindow.qml
|
||||
//
|
||||
// Created by Bradley Austin Davis on 22 Jan 2016
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
|
||||
import "."
|
||||
|
||||
Window {
|
||||
id: root
|
||||
anchors.centerIn: parent
|
||||
ScrollingWindow {
|
||||
id: window
|
||||
modality: Qt.ApplicationModal
|
||||
destroyOnCloseButton: true
|
||||
destroyOnInvisible: true
|
||||
frame: ModalFrame{}
|
||||
destroyOnHidden: true
|
||||
frame: ModalFrame { }
|
||||
|
||||
property int colorScheme: hifi.colorSchemes.light
|
||||
property bool draggable: false
|
||||
|
||||
signal frameClicked();
|
||||
|
||||
anchors.centerIn: draggable ? undefined : parent
|
||||
}
|
||||
|
||||
|
||||
|
|
157
interface/resources/qml/windows/ScrollingWindow.qml
Normal file
157
interface/resources/qml/windows/ScrollingWindow.qml
Normal file
|
@ -0,0 +1,157 @@
|
|||
//
|
||||
// Window.qml
|
||||
//
|
||||
// Created by Bradley Austin Davis on 12 Jan 2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
import QtGraphicalEffects 1.0
|
||||
|
||||
import "."
|
||||
import "../styles-uit"
|
||||
|
||||
// FIXME how do I set the initial position of a window without
|
||||
// overriding places where the a individual client of the window
|
||||
// might be setting the position with a Settings{} element?
|
||||
|
||||
// FIXME how to I enable dragging without allowing the window to lay outside
|
||||
// of the desktop? How do I ensure when the desktop resizes all the windows
|
||||
// are still at least partially visible?
|
||||
Window {
|
||||
id: window
|
||||
HifiConstants { id: hifi }
|
||||
children: [ swallower, frame, pane, activator ]
|
||||
|
||||
property var footer: Item { } // Optional static footer at the bottom of the dialog.
|
||||
|
||||
// Scrollable window content.
|
||||
// FIXME this should not define any visual content in this type. The base window
|
||||
// type should only consist of logic sized areas, with nothing drawn (although the
|
||||
// default value for the frame property does include visual decorations)
|
||||
property var pane: Item {
|
||||
property bool isScrolling: scrollView.height < scrollView.contentItem.height
|
||||
property int contentWidth: scrollView.width - (isScrolling ? 10 : 0)
|
||||
property int scrollHeight: scrollView.height
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.rightMargin: isScrolling ? 11 : 0
|
||||
|
||||
Rectangle {
|
||||
id: contentBackground
|
||||
anchors.fill: parent
|
||||
anchors.rightMargin: parent.isScrolling ? 11 : 0
|
||||
color: hifi.colors.baseGray
|
||||
visible: !window.hideBackground && modality != Qt.ApplicationModal
|
||||
}
|
||||
|
||||
|
||||
LinearGradient {
|
||||
visible: !window.hideBackground && gradientsSupported && modality != Qt.ApplicationModal
|
||||
anchors.top: contentBackground.bottom
|
||||
anchors.left: contentBackground.left
|
||||
width: contentBackground.width - 1
|
||||
height: 4
|
||||
start: Qt.point(0, 0)
|
||||
end: Qt.point(0, 4)
|
||||
gradient: Gradient {
|
||||
GradientStop { position: 0.0; color: hifi.colors.darkGray }
|
||||
GradientStop { position: 1.0; color: hifi.colors.darkGray0 }
|
||||
}
|
||||
cached: true
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
id: scrollView
|
||||
contentItem: content
|
||||
horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff
|
||||
verticalScrollBarPolicy: Qt.ScrollBarAsNeeded
|
||||
anchors.fill: parent
|
||||
anchors.rightMargin: parent.isScrolling ? 1 : 0
|
||||
anchors.bottomMargin: footer.height > 0 ? footerPane.height : 0
|
||||
|
||||
style: ScrollViewStyle {
|
||||
|
||||
padding.right: -7 // Move to right away from content.
|
||||
|
||||
handle: Item {
|
||||
implicitWidth: 8
|
||||
Rectangle {
|
||||
radius: 4
|
||||
color: hifi.colors.white30
|
||||
anchors {
|
||||
fill: parent
|
||||
leftMargin: 2 // Finesse size and position.
|
||||
topMargin: 1
|
||||
bottomMargin: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scrollBarBackground: Item {
|
||||
implicitWidth: 10
|
||||
Rectangle {
|
||||
color: hifi.colors.darkGray30
|
||||
radius: 4
|
||||
anchors {
|
||||
fill: parent
|
||||
topMargin: -1 // Finesse size
|
||||
bottomMargin: -2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
incrementControl: Item {
|
||||
visible: false
|
||||
}
|
||||
|
||||
decrementControl: Item {
|
||||
visible: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
// Optional non-scrolling footer.
|
||||
id: footerPane
|
||||
anchors {
|
||||
left: parent.left
|
||||
bottom: parent.bottom
|
||||
}
|
||||
width: parent.contentWidth
|
||||
height: footer.height + 2 * hifi.dimensions.contentSpacing.y + 3
|
||||
color: hifi.colors.baseGray
|
||||
visible: footer.height > 0
|
||||
|
||||
Item {
|
||||
// Horizontal rule.
|
||||
anchors.fill: parent
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
y: 1 // Stop displaying content just above horizontal rule/=.
|
||||
color: hifi.colors.baseGrayShadow
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
y: 2
|
||||
color: hifi.colors.baseGrayHighlight
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: 3 // Horizontal rule.
|
||||
children: [ footer ]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
96
interface/resources/qml/windows/ToolFrame.qml
Normal file
96
interface/resources/qml/windows/ToolFrame.qml
Normal file
|
@ -0,0 +1,96 @@
|
|||
//
|
||||
// DefaultFrame.qml
|
||||
//
|
||||
// Created by Bradley Austin Davis on 12 Jan 2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtGraphicalEffects 1.0
|
||||
|
||||
import "."
|
||||
import "../styles-uit"
|
||||
|
||||
Frame {
|
||||
HifiConstants { id: hifi }
|
||||
property bool horizontalSpacers: false
|
||||
property bool verticalSpacers: false
|
||||
|
||||
Rectangle {
|
||||
// Dialog frame
|
||||
id: frameContent
|
||||
readonly property int frameMargin: 6
|
||||
readonly property int frameMarginLeft: frameMargin + (horizontalSpacers ? 12 : 0)
|
||||
readonly property int frameMarginRight: frameMargin + (horizontalSpacers ? 12 : 0)
|
||||
readonly property int frameMarginTop: frameMargin + (verticalSpacers ? 12 : 0)
|
||||
readonly property int frameMarginBottom: frameMargin + (verticalSpacers ? 12 : 0)
|
||||
|
||||
Rectangle {
|
||||
visible: horizontalSpacers
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 6
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 8
|
||||
height: window.height
|
||||
color: "gray";
|
||||
radius: 4
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: horizontalSpacers
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 6
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 8
|
||||
height: window.height
|
||||
color: "gray";
|
||||
radius: 4
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: verticalSpacers
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 6
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
height: 8
|
||||
width: window.width
|
||||
color: "gray";
|
||||
radius: 4
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: verticalSpacers
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 6
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
height: 8
|
||||
width: window.width
|
||||
color: "gray";
|
||||
radius: 4
|
||||
}
|
||||
|
||||
anchors {
|
||||
leftMargin: -frameMarginLeft
|
||||
rightMargin: -frameMarginRight
|
||||
topMargin: -frameMarginTop
|
||||
bottomMargin: -frameMarginBottom
|
||||
}
|
||||
anchors.fill: parent
|
||||
color: hifi.colors.baseGrayHighlight40
|
||||
border {
|
||||
width: hifi.dimensions.borderWidth
|
||||
color: hifi.colors.faintGray50
|
||||
}
|
||||
radius: hifi.dimensions.borderRadius / 2
|
||||
|
||||
// Enable dragging of the window
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
drag.target: window
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,20 @@
|
|||
//
|
||||
// Window.qml
|
||||
//
|
||||
// Created by Bradley Austin Davis on 12 Jan 2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
import QtGraphicalEffects 1.0
|
||||
|
||||
import "."
|
||||
import "../styles"
|
||||
import "../styles-uit"
|
||||
|
||||
// FIXME how do I set the initial position of a window without
|
||||
// overriding places where the a individual client of the window
|
||||
|
@ -15,16 +26,36 @@ import "../styles"
|
|||
Fadable {
|
||||
id: window
|
||||
HifiConstants { id: hifi }
|
||||
|
||||
//
|
||||
// Signals
|
||||
//
|
||||
signal windowDestroyed();
|
||||
|
||||
//
|
||||
// Native properties
|
||||
//
|
||||
|
||||
// The Window size is the size of the content, while the frame
|
||||
// decorations can extend outside it.
|
||||
implicitHeight: content ? content.height : 0
|
||||
implicitWidth: content ? content.width : 0
|
||||
x: desktop.invalid_position; y: desktop.invalid_position;
|
||||
enabled: visible
|
||||
children: [ swallower, frame, content, activator ]
|
||||
|
||||
signal windowDestroyed();
|
||||
//
|
||||
// Custom properties
|
||||
//
|
||||
|
||||
property int modality: Qt.NonModal
|
||||
// Corresponds to the window shown / hidden state AS DISTINCT from window visibility.
|
||||
// Window visibility should NOT be used as a proxy for any other behavior.
|
||||
property bool shown: true
|
||||
// FIXME workaround to deal with the face that some visual items are defined here,
|
||||
// when they should be moved to a frame derived type
|
||||
property bool hideBackground: false
|
||||
visible: shown
|
||||
enabled: visible
|
||||
readonly property bool topLevelWindow: true
|
||||
property string title
|
||||
// Should the window be closable control?
|
||||
|
@ -34,17 +65,23 @@ Fadable {
|
|||
// Should hitting the close button hide or destroy the window?
|
||||
property bool destroyOnCloseButton: true
|
||||
// Should hiding the window destroy it or just hide it?
|
||||
property bool destroyOnInvisible: false
|
||||
// FIXME support for pinned / unpinned pending full design
|
||||
// property bool pinnable: false
|
||||
// property bool pinned: false
|
||||
property bool destroyOnHidden: false
|
||||
property bool pinnable: true
|
||||
property bool pinned: false
|
||||
property bool resizable: false
|
||||
property bool gradientsSupported: desktop.gradientsSupported
|
||||
property int colorScheme: hifi.colorSchemes.dark
|
||||
|
||||
property vector2d minSize: Qt.vector2d(100, 100)
|
||||
property vector2d maxSize: Qt.vector2d(1280, 720)
|
||||
property vector2d maxSize: Qt.vector2d(1280, 800)
|
||||
|
||||
// The content to place inside the window, determined by the client
|
||||
default property var content
|
||||
|
||||
property var footer: Item { } // Optional static footer at the bottom of the dialog.
|
||||
|
||||
function setDefaultFocus() {} // Default function; can be overridden by dialogs.
|
||||
|
||||
property var rectifier: Timer {
|
||||
property bool executing: false;
|
||||
interval: 100
|
||||
|
@ -65,20 +102,15 @@ Fadable {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
onXChanged: rectifier.begin();
|
||||
onYChanged: rectifier.begin();
|
||||
|
||||
// This mouse area serves to raise the window. To function, it must live
|
||||
// in the window and have a higher Z-order than the content, but follow
|
||||
// the position and size of frame decoration
|
||||
property var activator: MouseArea {
|
||||
width: frame.decoration.width
|
||||
height: frame.decoration.height
|
||||
x: frame.decoration.anchors.margins
|
||||
y: frame.decoration.anchors.topMargin
|
||||
width: frame.decoration ? frame.decoration.width : window.width
|
||||
height: frame.decoration ? frame.decoration.height : window.height
|
||||
x: frame.decoration ? frame.decoration.anchors.leftMargin : 0
|
||||
y: frame.decoration ? frame.decoration.anchors.topMargin : 0
|
||||
propagateComposedEvents: true
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.AllButtons
|
||||
enabled: window.visible
|
||||
onPressed: {
|
||||
|
@ -92,10 +124,10 @@ Fadable {
|
|||
// to prevent things like mouse wheel events from reaching the application and changing
|
||||
// the camera if the user is scrolling through a list and gets to the end.
|
||||
property var swallower: MouseArea {
|
||||
width: frame.decoration.width
|
||||
height: frame.decoration.height
|
||||
x: frame.decoration.anchors.margins
|
||||
y: frame.decoration.anchors.topMargin
|
||||
width: frame.decoration ? frame.decoration.width : window.width
|
||||
height: frame.decoration ? frame.decoration.height : window.height
|
||||
x: frame.decoration ? frame.decoration.anchors.leftMargin : 0
|
||||
y: frame.decoration ? frame.decoration.anchors.topMargin : 0
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.AllButtons
|
||||
enabled: window.visible
|
||||
|
@ -106,71 +138,119 @@ Fadable {
|
|||
onWheel: {}
|
||||
}
|
||||
|
||||
|
||||
// Default to a standard frame. Can be overriden to provide custom
|
||||
// frame styles, like a full desktop frame to simulate a modal window
|
||||
property var frame: DefaultFrame { }
|
||||
property var frame: DefaultFrame {
|
||||
//window: window
|
||||
}
|
||||
|
||||
|
||||
children: [ swallower, frame, content, activator ]
|
||||
|
||||
//
|
||||
// Handlers
|
||||
//
|
||||
Component.onCompleted: {
|
||||
window.parentChanged.connect(raise);
|
||||
raise();
|
||||
centerOrReposition();
|
||||
setDefaultFocus();
|
||||
d.centerOrReposition();
|
||||
d.updateVisibility(shown);
|
||||
}
|
||||
Component.onDestruction: {
|
||||
window.parentChanged.disconnect(raise); // Prevent warning on shutdown
|
||||
windowDestroyed();
|
||||
}
|
||||
|
||||
function centerOrReposition() {
|
||||
if (x == desktop.invalid_position && y == desktop.invalid_position) {
|
||||
desktop.centerOnVisible(window);
|
||||
} else {
|
||||
desktop.repositionOnVisible(window);
|
||||
}
|
||||
}
|
||||
onXChanged: rectifier.begin();
|
||||
onYChanged: rectifier.begin();
|
||||
|
||||
onShownChanged: d.updateVisibility(shown)
|
||||
|
||||
onVisibleChanged: {
|
||||
if (!visible && destroyOnInvisible) {
|
||||
destroy();
|
||||
return;
|
||||
}
|
||||
if (visible) {
|
||||
raise();
|
||||
}
|
||||
enabled = visible
|
||||
|
||||
if (visible && parent) {
|
||||
centerOrReposition();
|
||||
d.centerOrReposition();
|
||||
}
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
|
||||
readonly property alias pinned: window.pinned
|
||||
readonly property alias shown: window.shown
|
||||
readonly property alias modality: window.modality;
|
||||
|
||||
function getTargetVisibility() {
|
||||
if (!window.shown) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (modality !== Qt.NonModal) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (pinned) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (desktop && !desktop.pinned) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// The force flag causes all windows to fade back in, because a window was shown
|
||||
readonly property alias visible: window.visible
|
||||
function updateVisibility(force) {
|
||||
if (force && !pinned && desktop.pinned) {
|
||||
// Change the pinned state (which in turn will call us again)
|
||||
desktop.pinned = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var targetVisibility = getTargetVisibility();
|
||||
if (targetVisibility === visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (targetVisibility) {
|
||||
fadeIn(function() {
|
||||
if (force) {
|
||||
window.raise();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
fadeOut(function() {
|
||||
if (!window.shown && window.destroyOnHidden) {
|
||||
window.destroy();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function centerOrReposition() {
|
||||
if (x == desktop.invalid_position && y == desktop.invalid_position) {
|
||||
desktop.centerOnVisible(window);
|
||||
} else {
|
||||
desktop.repositionOnVisible(window);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// When the desktop pinned state changes, automatically handle the current windows
|
||||
Connections { target: desktop; onPinnedChanged: d.updateVisibility() }
|
||||
|
||||
|
||||
function raise() {
|
||||
if (visible && parent) {
|
||||
desktop.raise(window)
|
||||
}
|
||||
}
|
||||
|
||||
function pin() {
|
||||
// pinned = ! pinned
|
||||
function setPinned() {
|
||||
pinned = !pinned
|
||||
}
|
||||
|
||||
// our close function performs the same way as the OffscreenUI class:
|
||||
// don't do anything but manipulate the targetVisible flag and let the other
|
||||
// mechanisms decide if the window should be destroyed after the close
|
||||
// animation completes
|
||||
// FIXME using this close function messes up the visibility signals received by the
|
||||
// type and it's derived types
|
||||
// function close() {
|
||||
// console.log("Closing " + window)
|
||||
// if (destroyOnCloseButton) {
|
||||
// destroyOnInvisible = true
|
||||
// }
|
||||
// visible = false;
|
||||
// }
|
||||
|
||||
function framedRect() {
|
||||
if (!frame || !frame.decoration) {
|
||||
return Qt.rect(0, 0, window.width, window.height)
|
||||
|
@ -180,7 +260,6 @@ Fadable {
|
|||
window.height - frame.decoration.anchors.topMargin - frame.decoration.anchors.bottomMargin)
|
||||
}
|
||||
|
||||
|
||||
Keys.onPressed: {
|
||||
switch(event.key) {
|
||||
case Qt.Key_Control:
|
||||
|
@ -189,10 +268,9 @@ Fadable {
|
|||
case Qt.Key_Alt:
|
||||
break;
|
||||
|
||||
|
||||
case Qt.Key_W:
|
||||
if (window.closable && (event.modifiers === Qt.ControlModifier)) {
|
||||
visible = false
|
||||
shown = false
|
||||
event.accepted = true
|
||||
}
|
||||
// fall through
|
||||
|
|
|
@ -68,6 +68,7 @@
|
|||
#include <input-plugins/InputPlugin.h>
|
||||
#include <controllers/UserInputMapper.h>
|
||||
#include <controllers/StateController.h>
|
||||
#include <UserActivityLoggerScriptingInterface.h>
|
||||
#include <LogHandler.h>
|
||||
#include <MainWindow.h>
|
||||
#include <MessagesClient.h>
|
||||
|
@ -131,6 +132,7 @@
|
|||
#include "scripting/WebWindowClass.h"
|
||||
#include "scripting/WindowScriptingInterface.h"
|
||||
#include "scripting/ControllerScriptingInterface.h"
|
||||
#include "scripting/ToolbarScriptingInterface.h"
|
||||
#include "scripting/RatesScriptingInterface.h"
|
||||
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
|
||||
#include "SpeechRecognizer.h"
|
||||
|
@ -149,6 +151,8 @@
|
|||
#include "InterfaceParentFinder.h"
|
||||
|
||||
#include "FrameTimingsScriptingInterface.h"
|
||||
#include <GPUIdent.h>
|
||||
#include <gl/GLHelpers.h>
|
||||
|
||||
// On Windows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU
|
||||
// FIXME seems to be broken.
|
||||
|
@ -435,7 +439,8 @@ bool setupEssentials(int& argc, char** argv) {
|
|||
DependencyManager::set<WindowScriptingInterface>();
|
||||
DependencyManager::set<HMDScriptingInterface>();
|
||||
DependencyManager::set<ResourceScriptingInterface>();
|
||||
|
||||
DependencyManager::set<ToolbarScriptingInterface>();
|
||||
DependencyManager::set<UserActivityLoggerScriptingInterface>();
|
||||
|
||||
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
|
||||
DependencyManager::set<SpeechRecognizer>();
|
||||
|
@ -673,10 +678,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
|||
accountManager->setIsAgent(true);
|
||||
accountManager->setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL);
|
||||
|
||||
// sessionRunTime will be reset soon by loadSettings. Grab it now to get previous session value.
|
||||
// The value will be 0 if the user blew away settings this session, which is both a feature and a bug.
|
||||
UserActivityLogger::getInstance().launch(applicationVersion(), _previousSessionCrashed, sessionRunTime.get());
|
||||
|
||||
auto addressManager = DependencyManager::get<AddressManager>();
|
||||
|
||||
// use our MyAvatar position and quat for address manager path
|
||||
|
@ -766,6 +767,39 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
|||
// Make sure we don't time out during slow operations at startup
|
||||
updateHeartbeat();
|
||||
|
||||
|
||||
// sessionRunTime will be reset soon by loadSettings. Grab it now to get previous session value.
|
||||
// The value will be 0 if the user blew away settings this session, which is both a feature and a bug.
|
||||
auto gpuIdent = GPUIdent::getInstance();
|
||||
auto glContextData = getGLContextData();
|
||||
QJsonObject properties = {
|
||||
{ "previousSessionCrashed", _previousSessionCrashed },
|
||||
{ "previousSessionRuntime", sessionRunTime.get() },
|
||||
{ "cpu_architecture", QSysInfo::currentCpuArchitecture() },
|
||||
{ "kernel_type", QSysInfo::kernelType() },
|
||||
{ "kernel_version", QSysInfo::kernelVersion() },
|
||||
{ "os_type", QSysInfo::productType() },
|
||||
{ "os_version", QSysInfo::productVersion() },
|
||||
{ "gpu_name", gpuIdent->getName() },
|
||||
{ "gpu_driver", gpuIdent->getDriver() },
|
||||
{ "gpu_memory", static_cast<qint64>(gpuIdent->getMemory()) },
|
||||
{ "gl_version_int", glVersionToInteger(glContextData.value("version").toString()) },
|
||||
{ "gl_version", glContextData["version"] },
|
||||
{ "gl_vender", glContextData["vendor"] },
|
||||
{ "gl_sl_version", glContextData["slVersion"] },
|
||||
{ "gl_renderer", glContextData["renderer"] }
|
||||
};
|
||||
auto macVersion = QSysInfo::macVersion();
|
||||
if (macVersion != QSysInfo::MV_None) {
|
||||
properties["os_osx_version"] = QSysInfo::macVersion();
|
||||
}
|
||||
auto windowsVersion = QSysInfo::windowsVersion();
|
||||
if (windowsVersion != QSysInfo::WV_None) {
|
||||
properties["os_win_version"] = QSysInfo::windowsVersion();
|
||||
}
|
||||
UserActivityLogger::getInstance().logAction("launch", properties);
|
||||
|
||||
|
||||
// Tell our entity edit sender about our known jurisdictions
|
||||
_entityEditSender.setServerJurisdictions(&_entityServerJurisdictions);
|
||||
_entityEditSender.setMyAvatar(getMyAvatar());
|
||||
|
@ -1061,6 +1095,89 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
|||
}
|
||||
});
|
||||
|
||||
// Add periodic checks to send user activity data
|
||||
static int CHECK_NEARBY_AVATARS_INTERVAL_MS = 10000;
|
||||
static int SEND_STATS_INTERVAL_MS = 10000;
|
||||
static int NEARBY_AVATAR_RADIUS_METERS = 10;
|
||||
|
||||
// Periodically send fps as a user activity event
|
||||
QTimer* sendStatsTimer = new QTimer(this);
|
||||
sendStatsTimer->setInterval(SEND_STATS_INTERVAL_MS);
|
||||
connect(sendStatsTimer, &QTimer::timeout, this, [this]() {
|
||||
QJsonObject properties = {};
|
||||
MemoryInfo memInfo;
|
||||
if (getMemoryInfo(memInfo)) {
|
||||
properties["system_memory_total"] = static_cast<qint64>(memInfo.totalMemoryBytes);
|
||||
properties["system_memory_used"] = static_cast<qint64>(memInfo.usedMemoryBytes);
|
||||
properties["process_memory_used"] = static_cast<qint64>(memInfo.processUsedMemoryBytes);
|
||||
}
|
||||
|
||||
auto displayPlugin = qApp->getActiveDisplayPlugin();
|
||||
|
||||
properties["fps"] = _frameCounter.rate();
|
||||
properties["present_rate"] = displayPlugin->presentRate();
|
||||
properties["new_frame_present_rate"] = displayPlugin->newFramePresentRate();
|
||||
properties["dropped_frame_rate"] = displayPlugin->droppedFrameRate();
|
||||
properties["sim_rate"] = getAverageSimsPerSecond();
|
||||
properties["avatar_sim_rate"] = getAvatarSimrate();
|
||||
|
||||
auto bandwidthRecorder = DependencyManager::get<BandwidthRecorder>();
|
||||
properties["packet_rate_in"] = bandwidthRecorder->getCachedTotalAverageInputPacketsPerSecond();
|
||||
properties["packet_rate_out"] = bandwidthRecorder->getCachedTotalAverageOutputPacketsPerSecond();
|
||||
properties["kbps_in"] = bandwidthRecorder->getCachedTotalAverageInputKilobitsPerSecond();
|
||||
properties["kbps_out"] = bandwidthRecorder->getCachedTotalAverageOutputKilobitsPerSecond();
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
SharedNodePointer entityServerNode = nodeList->soloNodeOfType(NodeType::EntityServer);
|
||||
SharedNodePointer audioMixerNode = nodeList->soloNodeOfType(NodeType::AudioMixer);
|
||||
SharedNodePointer avatarMixerNode = nodeList->soloNodeOfType(NodeType::AvatarMixer);
|
||||
SharedNodePointer assetServerNode = nodeList->soloNodeOfType(NodeType::AssetServer);
|
||||
SharedNodePointer messagesMixerNode = nodeList->soloNodeOfType(NodeType::MessagesMixer);
|
||||
properties["entity_ping"] = entityServerNode ? entityServerNode->getPingMs() : -1;
|
||||
properties["audio_ping"] = audioMixerNode ? audioMixerNode->getPingMs() : -1;
|
||||
properties["avatar_ping"] = avatarMixerNode ? avatarMixerNode->getPingMs() : -1;
|
||||
properties["asset_ping"] = assetServerNode ? assetServerNode->getPingMs() : -1;
|
||||
properties["messages_ping"] = messagesMixerNode ? messagesMixerNode->getPingMs() : -1;
|
||||
|
||||
auto loadingRequests = ResourceCache::getLoadingRequests();
|
||||
properties["active_downloads"] = loadingRequests.size();
|
||||
properties["pending_downloads"] = ResourceCache::getPendingRequestCount();
|
||||
|
||||
properties["throttled"] = _displayPlugin ? _displayPlugin->isThrottled() : false;
|
||||
|
||||
UserActivityLogger::getInstance().logAction("stats", properties);
|
||||
});
|
||||
sendStatsTimer->start();
|
||||
|
||||
|
||||
// Periodically check for count of nearby avatars
|
||||
static int lastCountOfNearbyAvatars = -1;
|
||||
QTimer* checkNearbyAvatarsTimer = new QTimer(this);
|
||||
checkNearbyAvatarsTimer->setInterval(CHECK_NEARBY_AVATARS_INTERVAL_MS);
|
||||
connect(checkNearbyAvatarsTimer, &QTimer::timeout, this, [this]() {
|
||||
auto avatarManager = DependencyManager::get<AvatarManager>();
|
||||
int nearbyAvatars = avatarManager->numberOfAvatarsInRange(avatarManager->getMyAvatar()->getPosition(),
|
||||
NEARBY_AVATAR_RADIUS_METERS) - 1;
|
||||
if (nearbyAvatars != lastCountOfNearbyAvatars) {
|
||||
lastCountOfNearbyAvatars = nearbyAvatars;
|
||||
UserActivityLogger::getInstance().logAction("nearby_avatars", { { "count", nearbyAvatars } });
|
||||
}
|
||||
});
|
||||
checkNearbyAvatarsTimer->start();
|
||||
|
||||
// Track user activity event when we receive a mute packet
|
||||
auto onMutedByMixer = []() {
|
||||
UserActivityLogger::getInstance().logAction("received_mute_packet");
|
||||
};
|
||||
connect(DependencyManager::get<AudioClient>().data(), &AudioClient::mutedByMixer, this, onMutedByMixer);
|
||||
|
||||
// Track when the address bar is opened
|
||||
auto onAddressBarToggled = [this]() {
|
||||
// Record time
|
||||
UserActivityLogger::getInstance().logAction("opened_address_bar", { { "uptime_ms", _sessionRunTimer.elapsed() } });
|
||||
};
|
||||
connect(DependencyManager::get<DialogsManager>().data(), &DialogsManager::addressBarToggled, this, onAddressBarToggled);
|
||||
|
||||
// Make sure we don't time out during slow operations at startup
|
||||
updateHeartbeat();
|
||||
|
||||
|
@ -1419,7 +1536,7 @@ void Application::initializeUi() {
|
|||
|
||||
rootContext->setContextProperty("Overlays", &_overlays);
|
||||
rootContext->setContextProperty("Window", DependencyManager::get<WindowScriptingInterface>().data());
|
||||
rootContext->setContextProperty("Menu", MenuScriptingInterface::getInstance());
|
||||
rootContext->setContextProperty("MenuInterface", MenuScriptingInterface::getInstance());
|
||||
rootContext->setContextProperty("Stats", Stats::getInstance());
|
||||
rootContext->setContextProperty("Settings", SettingsScriptingInterface::getInstance());
|
||||
rootContext->setContextProperty("ScriptDiscoveryService", DependencyManager::get<ScriptEngines>().data());
|
||||
|
@ -2070,7 +2187,8 @@ void Application::keyPressEvent(QKeyEvent* event) {
|
|||
case Qt::Key_X:
|
||||
if (isShifted && isMeta) {
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
offscreenUi->getRootContext()->engine()->clearComponentCache();
|
||||
offscreenUi->togglePinned();
|
||||
//offscreenUi->getRootContext()->engine()->clearComponentCache();
|
||||
//OffscreenUi::information("Debugging", "Component cache cleared");
|
||||
// placeholder for dialogs being converted to QML.
|
||||
}
|
||||
|
@ -2719,7 +2837,6 @@ void Application::idle(float nsecsElapsed) {
|
|||
if (firstIdle) {
|
||||
firstIdle = false;
|
||||
connect(offscreenUi.data(), &OffscreenUi::showDesktop, this, &Application::showDesktop);
|
||||
_overlayConductor.setEnabled(Menu::getInstance()->isOptionChecked(MenuOption::Overlays));
|
||||
}
|
||||
|
||||
PROFILE_RANGE(__FUNCTION__);
|
||||
|
@ -3219,13 +3336,13 @@ void Application::updateThreads(float deltaTime) {
|
|||
}
|
||||
|
||||
void Application::toggleOverlays() {
|
||||
auto newOverlaysVisible = !_overlayConductor.getEnabled();
|
||||
Menu::getInstance()->setIsOptionChecked(MenuOption::Overlays, newOverlaysVisible);
|
||||
_overlayConductor.setEnabled(newOverlaysVisible);
|
||||
auto menu = Menu::getInstance();
|
||||
menu->setIsOptionChecked(MenuOption::Overlays, menu->isOptionChecked(MenuOption::Overlays));
|
||||
}
|
||||
|
||||
void Application::setOverlaysVisible(bool visible) {
|
||||
_overlayConductor.setEnabled(visible);
|
||||
auto menu = Menu::getInstance();
|
||||
menu->setIsOptionChecked(MenuOption::Overlays, true);
|
||||
}
|
||||
|
||||
void Application::cycleCamera() {
|
||||
|
@ -4210,6 +4327,7 @@ void Application::resetSensors(bool andReload) {
|
|||
DependencyManager::get<DdeFaceTracker>()->reset();
|
||||
DependencyManager::get<EyeTracker>()->reset();
|
||||
getActiveDisplayPlugin()->resetSensors();
|
||||
_overlayConductor.centerUI();
|
||||
getMyAvatar()->reset(andReload);
|
||||
QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(), "reset", Qt::QueuedConnection);
|
||||
}
|
||||
|
@ -4526,6 +4644,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
|
|||
RayToOverlayIntersectionResultFromScriptValue);
|
||||
|
||||
scriptEngine->registerGlobalObject("Desktop", DependencyManager::get<DesktopScriptingInterface>().data());
|
||||
scriptEngine->registerGlobalObject("Toolbars", DependencyManager::get<ToolbarScriptingInterface>().data());
|
||||
|
||||
scriptEngine->registerGlobalObject("Window", DependencyManager::get<WindowScriptingInterface>().data());
|
||||
scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter,
|
||||
|
@ -4574,6 +4693,8 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
|
|||
|
||||
scriptEngine->registerGlobalObject("ScriptDiscoveryService", DependencyManager::get<ScriptEngines>().data());
|
||||
scriptEngine->registerGlobalObject("Reticle", getApplicationCompositor().getReticleInterface());
|
||||
|
||||
scriptEngine->registerGlobalObject("UserActivityLogger", DependencyManager::get<UserActivityLoggerScriptingInterface>().data());
|
||||
}
|
||||
|
||||
bool Application::canAcceptURL(const QString& urlString) const {
|
||||
|
@ -5176,6 +5297,11 @@ void Application::updateDisplayMode() {
|
|||
return;
|
||||
}
|
||||
|
||||
UserActivityLogger::getInstance().logAction("changed_display_mode", {
|
||||
{ "previous_display_mode", _displayPlugin ? _displayPlugin->getName() : "" },
|
||||
{ "display_mode", newDisplayPlugin ? newDisplayPlugin->getName() : "" }
|
||||
});
|
||||
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
|
||||
// Make the switch atomic from the perspective of other threads
|
||||
|
@ -5300,9 +5426,7 @@ void Application::readArgumentsFromLocalSocket() const {
|
|||
}
|
||||
|
||||
void Application::showDesktop() {
|
||||
if (!_overlayConductor.getEnabled()) {
|
||||
_overlayConductor.setEnabled(true);
|
||||
}
|
||||
Menu::getInstance()->setIsOptionChecked(MenuOption::Overlays, true);
|
||||
}
|
||||
|
||||
CompositorHelper& Application::getApplicationCompositor() const {
|
||||
|
|
|
@ -211,6 +211,8 @@ public:
|
|||
|
||||
float getRenderResolutionScale() const;
|
||||
|
||||
qint64 getCurrentSessionRuntime() const { return _sessionRunTimer.elapsed(); }
|
||||
|
||||
bool isAboutToQuit() const { return _aboutToQuit; }
|
||||
|
||||
// the isHMDMode is true whenever we use the interface from an HMD and not a standard flat display
|
||||
|
|
|
@ -80,7 +80,8 @@ void DiscoverabilityManager::updateLocation() {
|
|||
locationObject.insert(FRIENDS_ONLY_KEY_IN_LOCATION, (_mode.get() == Discoverability::Friends));
|
||||
|
||||
// if we have a session ID add it now, otherwise add a null value
|
||||
rootObject[SESSION_ID_KEY] = _sessionID.isEmpty() ? QJsonValue() : _sessionID;
|
||||
auto sessionID = accountManager->getSessionID();
|
||||
rootObject[SESSION_ID_KEY] = sessionID.isNull() ? QJsonValue() : sessionID.toString();
|
||||
|
||||
JSONCallbackParameters callbackParameters;
|
||||
callbackParameters.jsonCallbackReceiver = this;
|
||||
|
@ -110,11 +111,8 @@ void DiscoverabilityManager::updateLocation() {
|
|||
callbackParameters.jsonCallbackMethod = "handleHeartbeatResponse";
|
||||
|
||||
QJsonObject heartbeatObject;
|
||||
if (!_sessionID.isEmpty()) {
|
||||
heartbeatObject[SESSION_ID_KEY] = _sessionID;
|
||||
} else {
|
||||
heartbeatObject[SESSION_ID_KEY] = QJsonValue();
|
||||
}
|
||||
auto sessionID = accountManager->getSessionID();
|
||||
heartbeatObject[SESSION_ID_KEY] = sessionID.isNull() ? QJsonValue() : sessionID.toString();
|
||||
|
||||
accountManager->sendRequest(API_USER_HEARTBEAT_PATH, AccountManagerAuth::Optional,
|
||||
QNetworkAccessManager::PutOperation, callbackParameters,
|
||||
|
@ -126,11 +124,11 @@ void DiscoverabilityManager::handleHeartbeatResponse(QNetworkReply& requestReply
|
|||
auto dataObject = AccountManager::dataObjectFromResponse(requestReply);
|
||||
|
||||
if (!dataObject.isEmpty()) {
|
||||
_sessionID = dataObject[SESSION_ID_KEY].toString();
|
||||
auto sessionID = dataObject[SESSION_ID_KEY].toString();
|
||||
|
||||
// give that session ID to the account manager
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
accountManager->setSessionID(_sessionID);
|
||||
accountManager->setSessionID(sessionID);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -49,7 +49,6 @@ private:
|
|||
DiscoverabilityManager();
|
||||
|
||||
Setting::Handle<int> _mode;
|
||||
QString _sessionID;
|
||||
QJsonObject _lastLocationObject;
|
||||
};
|
||||
|
||||
|
|
|
@ -256,8 +256,7 @@ Menu::Menu() {
|
|||
UNSPECIFIED_POSITION, "Advanced");
|
||||
|
||||
// View > Overlays
|
||||
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Overlays, 0, true,
|
||||
qApp, SLOT(setOverlaysVisible(bool)));
|
||||
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Overlays, 0, true);
|
||||
|
||||
// Navigate menu ----------------------------------
|
||||
MenuWrapper* navigateMenu = addMenu("Navigate");
|
||||
|
|
|
@ -722,7 +722,7 @@ void MyAvatar::saveData() {
|
|||
settings.setValue("displayName", _displayName);
|
||||
settings.setValue("collisionSoundURL", _collisionSoundURL);
|
||||
settings.setValue("useSnapTurn", _useSnapTurn);
|
||||
settings.setValue("clearOverlayWhenDriving", _clearOverlayWhenDriving);
|
||||
settings.setValue("clearOverlayWhenMoving", _clearOverlayWhenMoving);
|
||||
|
||||
settings.endGroup();
|
||||
}
|
||||
|
@ -842,7 +842,7 @@ void MyAvatar::loadData() {
|
|||
setDisplayName(settings.value("displayName").toString());
|
||||
setCollisionSoundURL(settings.value("collisionSoundURL", DEFAULT_AVATAR_COLLISION_SOUND_URL).toString());
|
||||
setSnapTurn(settings.value("useSnapTurn", _useSnapTurn).toBool());
|
||||
setClearOverlayWhenDriving(settings.value("clearOverlayWhenDriving", _clearOverlayWhenDriving).toBool());
|
||||
setClearOverlayWhenMoving(settings.value("clearOverlayWhenMoving", _clearOverlayWhenMoving).toBool());
|
||||
|
||||
settings.endGroup();
|
||||
|
||||
|
@ -1248,8 +1248,7 @@ void MyAvatar::prepareForPhysicsSimulation() {
|
|||
|
||||
_characterController.setPositionAndOrientation(getPosition(), getOrientation());
|
||||
if (qApp->isHMDMode()) {
|
||||
bool hasDriveInput = fabsf(_driveKeys[TRANSLATE_X]) > 0.0f || fabsf(_driveKeys[TRANSLATE_Z]) > 0.0f;
|
||||
_follow.prePhysicsUpdate(*this, deriveBodyFromHMDSensor(), _bodySensorMatrix, hasDriveInput);
|
||||
_follow.prePhysicsUpdate(*this, deriveBodyFromHMDSensor(), _bodySensorMatrix, hasDriveInput());
|
||||
} else {
|
||||
_follow.deactivate();
|
||||
}
|
||||
|
@ -2134,3 +2133,7 @@ bool MyAvatar::didTeleport() {
|
|||
lastPosition = pos;
|
||||
return (changeInPosition.length() > MAX_AVATAR_MOVEMENT_PER_FRAME);
|
||||
}
|
||||
|
||||
bool MyAvatar::hasDriveInput() const {
|
||||
return fabsf(_driveKeys[TRANSLATE_X]) > 0.0f || fabsf(_driveKeys[TRANSLATE_Y]) > 0.0f || fabsf(_driveKeys[TRANSLATE_Z]) > 0.0f;
|
||||
}
|
||||
|
|
|
@ -159,8 +159,8 @@ public:
|
|||
|
||||
Q_INVOKABLE bool getSnapTurn() const { return _useSnapTurn; }
|
||||
Q_INVOKABLE void setSnapTurn(bool on) { _useSnapTurn = on; }
|
||||
Q_INVOKABLE bool getClearOverlayWhenDriving() const { return _clearOverlayWhenDriving; }
|
||||
Q_INVOKABLE void setClearOverlayWhenDriving(bool on) { _clearOverlayWhenDriving = on; }
|
||||
Q_INVOKABLE bool getClearOverlayWhenMoving() const { return _clearOverlayWhenMoving; }
|
||||
Q_INVOKABLE void setClearOverlayWhenMoving(bool on) { _clearOverlayWhenMoving = on; }
|
||||
|
||||
Q_INVOKABLE void setHMDLeanRecenterEnabled(bool value) { _hmdLeanRecenterEnabled = value; }
|
||||
Q_INVOKABLE bool getHMDLeanRecenterEnabled() const { return _hmdLeanRecenterEnabled; }
|
||||
|
@ -266,6 +266,8 @@ public:
|
|||
controller::Pose getLeftHandControllerPoseInAvatarFrame() const;
|
||||
controller::Pose getRightHandControllerPoseInAvatarFrame() const;
|
||||
|
||||
bool hasDriveInput() const;
|
||||
|
||||
Q_INVOKABLE void setCharacterControllerEnabled(bool enabled);
|
||||
Q_INVOKABLE bool getCharacterControllerEnabled();
|
||||
|
||||
|
@ -403,7 +405,7 @@ private:
|
|||
QString _fullAvatarModelName;
|
||||
QUrl _animGraphUrl {""};
|
||||
bool _useSnapTurn { true };
|
||||
bool _clearOverlayWhenDriving { false };
|
||||
bool _clearOverlayWhenMoving { true };
|
||||
|
||||
// cache of the current HMD sensor position and orientation
|
||||
// in sensor space.
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
#include "Application.h"
|
||||
#include "MainWindow.h"
|
||||
#include <display-plugins/CompositorHelper.h>
|
||||
|
||||
int DesktopScriptingInterface::getWidth() {
|
||||
QSize size = qApp->getWindow()->windowHandle()->screen()->virtualSize();
|
||||
|
@ -25,3 +26,8 @@ int DesktopScriptingInterface::getHeight() {
|
|||
QSize size = qApp->getWindow()->windowHandle()->screen()->virtualSize();
|
||||
return size.height();
|
||||
}
|
||||
|
||||
void DesktopScriptingInterface::setOverlayAlpha(float alpha) {
|
||||
qApp->getApplicationCompositor().setAlpha(alpha);
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,8 @@ class DesktopScriptingInterface : public QObject, public Dependency {
|
|||
Q_PROPERTY(int height READ getHeight) // Physical height of screen(s) including task bars and system menus
|
||||
|
||||
public:
|
||||
Q_INVOKABLE void setOverlayAlpha(float alpha);
|
||||
|
||||
int getWidth();
|
||||
int getHeight();
|
||||
};
|
||||
|
|
|
@ -105,3 +105,25 @@ QString HMDScriptingInterface::preferredAudioInput() const {
|
|||
QString HMDScriptingInterface::preferredAudioOutput() const {
|
||||
return qApp->getActiveDisplayPlugin()->getPreferredAudioOutDevice();
|
||||
}
|
||||
|
||||
bool HMDScriptingInterface::setHandLasers(int hands, bool enabled, const glm::vec4& color, const glm::vec3& direction) const {
|
||||
return qApp->getActiveDisplayPlugin()->setHandLaser(hands,
|
||||
enabled ? DisplayPlugin::HandLaserMode::Overlay : DisplayPlugin::HandLaserMode::None,
|
||||
color, direction);
|
||||
}
|
||||
|
||||
void HMDScriptingInterface::disableHandLasers(int hands) const {
|
||||
qApp->getActiveDisplayPlugin()->setHandLaser(hands, DisplayPlugin::HandLaserMode::None);
|
||||
}
|
||||
|
||||
bool HMDScriptingInterface::suppressKeyboard() {
|
||||
return qApp->getActiveDisplayPlugin()->suppressKeyboard();
|
||||
}
|
||||
|
||||
void HMDScriptingInterface::unsuppressKeyboard() {
|
||||
qApp->getActiveDisplayPlugin()->unsuppressKeyboard();
|
||||
}
|
||||
|
||||
bool HMDScriptingInterface::isKeyboardVisible() {
|
||||
return qApp->getActiveDisplayPlugin()->isKeyboardVisible();
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
//
|
||||
|
||||
// HMDScriptingInterface.h
|
||||
// interface/src/scripting
|
||||
//
|
||||
|
@ -12,6 +12,8 @@
|
|||
#ifndef hifi_HMDScriptingInterface_h
|
||||
#define hifi_HMDScriptingInterface_h
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include <QtScript/QScriptValue>
|
||||
class QScriptContext;
|
||||
class QScriptEngine;
|
||||
|
@ -31,12 +33,28 @@ public:
|
|||
Q_INVOKABLE glm::vec3 calculateRayUICollisionPoint(const glm::vec3& position, const glm::vec3& direction) const;
|
||||
Q_INVOKABLE glm::vec2 overlayFromWorldPoint(const glm::vec3& position) const;
|
||||
Q_INVOKABLE glm::vec3 worldPointFromOverlay(const glm::vec2& overlay) const;
|
||||
|
||||
Q_INVOKABLE glm::vec2 sphericalToOverlay(const glm::vec2 & sphericalPos) const;
|
||||
Q_INVOKABLE glm::vec2 overlayToSpherical(const glm::vec2 & overlayPos) const;
|
||||
Q_INVOKABLE QString preferredAudioInput() const;
|
||||
Q_INVOKABLE QString preferredAudioOutput() const;
|
||||
|
||||
Q_INVOKABLE bool setHandLasers(int hands, bool enabled, const glm::vec4& color, const glm::vec3& direction) const;
|
||||
|
||||
Q_INVOKABLE void disableHandLasers(int hands) const;
|
||||
/// Suppress the activation of any on-screen keyboard so that a script operation will
|
||||
/// not be interrupted by a keyboard popup
|
||||
/// Returns false if there is already an active keyboard displayed.
|
||||
/// Clients should re-enable the keyboard when the operation is complete and ensure
|
||||
/// that they balance any call to suppressKeyboard() that returns true with a corresponding
|
||||
/// call to unsuppressKeyboard() within a reasonable amount of time
|
||||
Q_INVOKABLE bool suppressKeyboard();
|
||||
|
||||
/// Enable the keyboard following a suppressKeyboard call
|
||||
Q_INVOKABLE void unsuppressKeyboard();
|
||||
|
||||
/// Query the display plugin to determine the current VR keyboard visibility
|
||||
Q_INVOKABLE bool isKeyboardVisible();
|
||||
|
||||
public:
|
||||
HMDScriptingInterface();
|
||||
static QScriptValue getHUDLookAtPosition2D(QScriptContext* context, QScriptEngine* engine);
|
||||
|
|
115
interface/src/scripting/ToolbarScriptingInterface.cpp
Normal file
115
interface/src/scripting/ToolbarScriptingInterface.cpp
Normal file
|
@ -0,0 +1,115 @@
|
|||
//
|
||||
// Created by Bradley Austin Davis on 2016-06-16
|
||||
// Copyright 2013-2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "ToolbarScriptingInterface.h"
|
||||
|
||||
#include <OffscreenUi.h>
|
||||
|
||||
class QmlWrapper : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
QmlWrapper(QObject* qmlObject, QObject* parent = nullptr)
|
||||
: QObject(parent), _qmlObject(qmlObject) {
|
||||
}
|
||||
|
||||
Q_INVOKABLE void writeProperty(QString propertyName, QVariant propertyValue) {
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
offscreenUi->executeOnUiThread([=] {
|
||||
_qmlObject->setProperty(propertyName.toStdString().c_str(), propertyValue);
|
||||
});
|
||||
}
|
||||
|
||||
Q_INVOKABLE void writeProperties(QVariant propertyMap) {
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
offscreenUi->executeOnUiThread([=] {
|
||||
QVariantMap map = propertyMap.toMap();
|
||||
for (const QString& key : map.keys()) {
|
||||
_qmlObject->setProperty(key.toStdString().c_str(), map[key]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Q_INVOKABLE QVariant readProperty(const QString& propertyName) {
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
return offscreenUi->returnFromUiThread([&]()->QVariant {
|
||||
return _qmlObject->property(propertyName.toStdString().c_str());
|
||||
});
|
||||
}
|
||||
|
||||
Q_INVOKABLE QVariant readProperties(const QVariant& propertyList) {
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
return offscreenUi->returnFromUiThread([&]()->QVariant {
|
||||
QVariantMap result;
|
||||
for (const QVariant& property : propertyList.toList()) {
|
||||
QString propertyString = property.toString();
|
||||
result.insert(propertyString, _qmlObject->property(propertyString.toStdString().c_str()));
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
protected:
|
||||
QObject* _qmlObject{ nullptr };
|
||||
};
|
||||
|
||||
|
||||
class ToolbarButtonProxy : public QmlWrapper {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ToolbarButtonProxy(QObject* qmlObject, QObject* parent = nullptr) : QmlWrapper(qmlObject, parent) {
|
||||
connect(qmlObject, SIGNAL(clicked()), this, SIGNAL(clicked()));
|
||||
}
|
||||
|
||||
signals:
|
||||
void clicked();
|
||||
};
|
||||
|
||||
class ToolbarProxy : public QmlWrapper {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ToolbarProxy(QObject* qmlObject, QObject* parent = nullptr) : QmlWrapper(qmlObject, parent) { }
|
||||
|
||||
Q_INVOKABLE QObject* addButton(const QVariant& properties) {
|
||||
QVariant resultVar;
|
||||
bool invokeResult = QMetaObject::invokeMethod(_qmlObject, "addButton", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QVariant, resultVar), Q_ARG(QVariant, properties));
|
||||
if (!invokeResult) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QObject* rawButton = qvariant_cast<QObject *>(resultVar);
|
||||
if (!rawButton) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return new ToolbarButtonProxy(rawButton, this);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
QObject* ToolbarScriptingInterface::getToolbar(const QString& toolbarId) {
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
auto desktop = offscreenUi->getDesktop();
|
||||
QVariant resultVar;
|
||||
bool invokeResult = QMetaObject::invokeMethod(desktop, "getToolbar", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QVariant, resultVar), Q_ARG(QVariant, toolbarId));
|
||||
if (!invokeResult) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QObject* rawToolbar = qvariant_cast<QObject *>(resultVar);
|
||||
if (!rawToolbar) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return new ToolbarProxy(rawToolbar);
|
||||
}
|
||||
|
||||
|
||||
#include "ToolbarScriptingInterface.moc"
|
26
interface/src/scripting/ToolbarScriptingInterface.h
Normal file
26
interface/src/scripting/ToolbarScriptingInterface.h
Normal file
|
@ -0,0 +1,26 @@
|
|||
//
|
||||
// Created by Bradley Austin Davis on 2016-06-16
|
||||
// Copyright 2013-2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_ToolbarScriptingInterface_h
|
||||
#define hifi_ToolbarScriptingInterface_h
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include <QtCore/QObject>
|
||||
|
||||
#include <DependencyManager.h>
|
||||
|
||||
class ToolbarProxy;
|
||||
|
||||
class ToolbarScriptingInterface : public QObject, public Dependency {
|
||||
Q_OBJECT
|
||||
public:
|
||||
Q_INVOKABLE QObject* getToolbar(const QString& toolbarId);
|
||||
};
|
||||
|
||||
#endif // hifi_ToolbarScriptingInterface_h
|
|
@ -17,179 +17,134 @@
|
|||
#include "OverlayConductor.h"
|
||||
|
||||
OverlayConductor::OverlayConductor() {
|
||||
|
||||
}
|
||||
|
||||
OverlayConductor::~OverlayConductor() {
|
||||
}
|
||||
|
||||
void OverlayConductor::update(float dt) {
|
||||
bool OverlayConductor::headOutsideOverlay() const {
|
||||
glm::mat4 hmdMat = qApp->getHMDSensorPose();
|
||||
glm::vec3 hmdPos = extractTranslation(hmdMat);
|
||||
glm::vec3 hmdForward = transformVectorFast(hmdMat, glm::vec3(0.0f, 0.0f, -1.0f));
|
||||
|
||||
updateMode();
|
||||
Transform uiTransform = qApp->getApplicationCompositor().getModelTransform();
|
||||
glm::vec3 uiPos = uiTransform.getTranslation();
|
||||
glm::vec3 uiForward = uiTransform.getRotation() * glm::vec3(0.0f, 0.0f, -1.0f);
|
||||
|
||||
switch (_mode) {
|
||||
case SITTING: {
|
||||
// when sitting, the overlay is at the origin, facing down the -z axis.
|
||||
// the camera is taken directly from the HMD.
|
||||
Transform identity;
|
||||
qApp->getApplicationCompositor().setModelTransform(identity);
|
||||
qApp->getApplicationCompositor().setCameraBaseTransform(identity);
|
||||
break;
|
||||
}
|
||||
case STANDING: {
|
||||
// when standing, the overlay is at a reference position, which is set when the overlay is
|
||||
// enabled. The camera is taken directly from the HMD, but in world space.
|
||||
// So the sensorToWorldMatrix must be applied.
|
||||
MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||
Transform t;
|
||||
t.evalFromRawMatrix(myAvatar->getSensorToWorldMatrix());
|
||||
qApp->getApplicationCompositor().setCameraBaseTransform(t);
|
||||
|
||||
// detect when head moves out side of sweet spot, or looks away.
|
||||
mat4 headMat = myAvatar->getSensorToWorldMatrix() * qApp->getHMDSensorPose();
|
||||
vec3 headWorldPos = extractTranslation(headMat);
|
||||
vec3 headForward = glm::quat_cast(headMat) * glm::vec3(0.0f, 0.0f, -1.0f);
|
||||
Transform modelXform = qApp->getApplicationCompositor().getModelTransform();
|
||||
vec3 compositorWorldPos = modelXform.getTranslation();
|
||||
vec3 compositorForward = modelXform.getRotation() * glm::vec3(0.0f, 0.0f, -1.0f);
|
||||
const float MAX_COMPOSITOR_DISTANCE = 0.6f;
|
||||
const float MAX_COMPOSITOR_ANGLE = 110.0f;
|
||||
if (_enabled && (glm::distance(headWorldPos, compositorWorldPos) > MAX_COMPOSITOR_DISTANCE ||
|
||||
glm::dot(headForward, compositorForward) < cosf(glm::radians(MAX_COMPOSITOR_ANGLE)))) {
|
||||
// fade out the overlay
|
||||
setEnabled(false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case FLAT:
|
||||
// do nothing
|
||||
break;
|
||||
const float MAX_COMPOSITOR_DISTANCE = 0.99f; // If you're 1m from center of ui sphere, you're at the surface.
|
||||
const float MAX_COMPOSITOR_ANGLE = 180.0f; // rotation check is effectively disabled
|
||||
if (glm::distance(uiPos, hmdPos) > MAX_COMPOSITOR_DISTANCE ||
|
||||
glm::dot(uiForward, hmdForward) < cosf(glm::radians(MAX_COMPOSITOR_ANGLE))) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void OverlayConductor::updateMode() {
|
||||
bool OverlayConductor::updateAvatarIsAtRest() {
|
||||
|
||||
MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||
if (myAvatar->getClearOverlayWhenDriving()) {
|
||||
float speed = glm::length(myAvatar->getVelocity());
|
||||
const float MIN_DRIVING = 0.2f;
|
||||
const float MAX_NOT_DRIVING = 0.01f;
|
||||
const quint64 REQUIRED_USECS_IN_NEW_MODE_BEFORE_INVISIBLE = 200 * 1000;
|
||||
const quint64 REQUIRED_USECS_IN_NEW_MODE_BEFORE_VISIBLE = 1000 * 1000;
|
||||
bool nowDriving = _driving; // Assume current _driving mode unless...
|
||||
if (speed > MIN_DRIVING) { // ... we're definitely moving...
|
||||
nowDriving = true;
|
||||
} else if (speed < MAX_NOT_DRIVING) { // ... or definitely not.
|
||||
nowDriving = false;
|
||||
}
|
||||
// Check that we're in this new mode for long enough to really trigger a transition.
|
||||
if (nowDriving == _driving) { // If there's no change in state, clear any attepted timer.
|
||||
_timeInPotentialMode = 0;
|
||||
} else if (_timeInPotentialMode == 0) { // We've just changed with no timer, so start timing now.
|
||||
_timeInPotentialMode = usecTimestampNow();
|
||||
} else if ((usecTimestampNow() - _timeInPotentialMode) > (nowDriving ? REQUIRED_USECS_IN_NEW_MODE_BEFORE_INVISIBLE : REQUIRED_USECS_IN_NEW_MODE_BEFORE_VISIBLE)) {
|
||||
_timeInPotentialMode = 0; // a real transition
|
||||
if (nowDriving) {
|
||||
_wantsOverlays = Menu::getInstance()->isOptionChecked(MenuOption::Overlays);
|
||||
} else { // reset when coming out of driving
|
||||
_mode = FLAT; // Seems appropriate to let things reset, below, after the following.
|
||||
// All reset of, e.g., room-scale location as though by apostrophe key, without all the other adjustments.
|
||||
qApp->getActiveDisplayPlugin()->resetSensors();
|
||||
myAvatar->reset(true, false, false);
|
||||
}
|
||||
if (_wantsOverlays) {
|
||||
setEnabled(!nowDriving, false);
|
||||
}
|
||||
_driving = nowDriving;
|
||||
} // Else haven't accumulated enough time in new mode, but keep timing.
|
||||
|
||||
const quint64 REST_ENABLE_TIME_USECS = 1000 * 1000; // 1 s
|
||||
const quint64 REST_DISABLE_TIME_USECS = 200 * 1000; // 200 ms
|
||||
|
||||
const float AT_REST_THRESHOLD = 0.01f;
|
||||
bool desiredAtRest = glm::length(myAvatar->getVelocity()) < AT_REST_THRESHOLD;
|
||||
if (desiredAtRest != _desiredAtRest) {
|
||||
// start timer
|
||||
_desiredAtRestTimer = usecTimestampNow() + (desiredAtRest ? REST_ENABLE_TIME_USECS : REST_DISABLE_TIME_USECS);
|
||||
}
|
||||
|
||||
Mode newMode;
|
||||
if (qApp->isHMDMode()) {
|
||||
newMode = SITTING;
|
||||
_desiredAtRest = desiredAtRest;
|
||||
|
||||
if (_desiredAtRestTimer != 0 && usecTimestampNow() > _desiredAtRestTimer) {
|
||||
// timer expired
|
||||
// change state!
|
||||
_currentAtRest = _desiredAtRest;
|
||||
// disable timer
|
||||
_desiredAtRestTimer = 0;
|
||||
}
|
||||
|
||||
return _currentAtRest;
|
||||
}
|
||||
|
||||
bool OverlayConductor::updateAvatarHasDriveInput() {
|
||||
MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||
|
||||
const quint64 DRIVE_ENABLE_TIME_USECS = 200 * 1000; // 200 ms
|
||||
const quint64 DRIVE_DISABLE_TIME_USECS = 1000 * 1000; // 1 s
|
||||
|
||||
bool desiredDriving = myAvatar->hasDriveInput();
|
||||
if (desiredDriving != _desiredDriving) {
|
||||
// start timer
|
||||
_desiredDrivingTimer = usecTimestampNow() + (desiredDriving ? DRIVE_ENABLE_TIME_USECS : DRIVE_DISABLE_TIME_USECS);
|
||||
}
|
||||
|
||||
_desiredDriving = desiredDriving;
|
||||
|
||||
if (_desiredDrivingTimer != 0 && usecTimestampNow() > _desiredDrivingTimer) {
|
||||
// timer expired
|
||||
// change state!
|
||||
_currentDriving = _desiredDriving;
|
||||
// disable timer
|
||||
_desiredDrivingTimer = 0;
|
||||
}
|
||||
|
||||
return _currentDriving;
|
||||
}
|
||||
|
||||
void OverlayConductor::centerUI() {
|
||||
// place the overlay at the current hmd position in sensor space
|
||||
auto camMat = cancelOutRollAndPitch(qApp->getHMDSensorPose());
|
||||
qApp->getApplicationCompositor().setModelTransform(Transform(camMat));
|
||||
}
|
||||
|
||||
void OverlayConductor::update(float dt) {
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
bool currentVisible = !offscreenUi->getDesktop()->property("pinned").toBool();
|
||||
|
||||
MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||
// centerUI when hmd mode is first enabled and mounted
|
||||
if (qApp->isHMDMode() && qApp->getActiveDisplayPlugin()->isDisplayVisible()) {
|
||||
if (!_hmdMode) {
|
||||
_hmdMode = true;
|
||||
centerUI();
|
||||
}
|
||||
} else {
|
||||
newMode = FLAT;
|
||||
_hmdMode = false;
|
||||
}
|
||||
|
||||
if (newMode != _mode) {
|
||||
switch (newMode) {
|
||||
case SITTING: {
|
||||
// enter the SITTING state
|
||||
// place the overlay at origin
|
||||
Transform identity;
|
||||
qApp->getApplicationCompositor().setModelTransform(identity);
|
||||
break;
|
||||
}
|
||||
case STANDING: { // STANDING mode is not currently used.
|
||||
// enter the STANDING state
|
||||
// place the overlay at the current hmd position in world space
|
||||
auto camMat = cancelOutRollAndPitch(myAvatar->getSensorToWorldMatrix() * qApp->getHMDSensorPose());
|
||||
Transform t;
|
||||
t.setTranslation(extractTranslation(camMat));
|
||||
t.setRotation(glm::quat_cast(camMat));
|
||||
qApp->getApplicationCompositor().setModelTransform(t);
|
||||
break;
|
||||
}
|
||||
bool prevDriving = _currentDriving;
|
||||
bool isDriving = updateAvatarHasDriveInput();
|
||||
bool drivingChanged = prevDriving != isDriving;
|
||||
bool isAtRest = updateAvatarIsAtRest();
|
||||
|
||||
case FLAT:
|
||||
// do nothing
|
||||
break;
|
||||
if (_flags & SuppressedByDrive) {
|
||||
if (!isDriving) {
|
||||
_flags &= ~SuppressedByDrive;
|
||||
}
|
||||
} else {
|
||||
if (myAvatar->getClearOverlayWhenMoving() && drivingChanged && isDriving) {
|
||||
_flags |= SuppressedByDrive;
|
||||
}
|
||||
}
|
||||
|
||||
_mode = newMode;
|
||||
|
||||
}
|
||||
|
||||
void OverlayConductor::setEnabled(bool enabled, bool toggleQmlEvents) {
|
||||
|
||||
if (enabled == _enabled) {
|
||||
return;
|
||||
if (_flags & SuppressedByHead) {
|
||||
if (isAtRest) {
|
||||
_flags &= ~SuppressedByHead;
|
||||
}
|
||||
} else {
|
||||
if (_hmdMode && headOutsideOverlay()) {
|
||||
_flags |= SuppressedByHead;
|
||||
}
|
||||
}
|
||||
|
||||
if (toggleQmlEvents) { // Could recurse on us with the wrong toggleQmlEvents flag, and not need in the !toggleQmlEvent case anyway.
|
||||
Menu::getInstance()->setIsOptionChecked(MenuOption::Overlays, enabled);
|
||||
}
|
||||
|
||||
_enabled = enabled; // set the new value
|
||||
|
||||
// if the new state is visible/enabled...
|
||||
if (_enabled) {
|
||||
// alpha fadeIn the overlay mesh.
|
||||
qApp->getApplicationCompositor().fadeIn();
|
||||
|
||||
// enable mouse clicks from script
|
||||
qApp->getOverlays().enable();
|
||||
|
||||
// enable QML events
|
||||
if (toggleQmlEvents) {
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
offscreenUi->getRootItem()->setEnabled(true);
|
||||
}
|
||||
|
||||
if (_mode == STANDING) {
|
||||
// place the overlay at the current hmd position in world space
|
||||
MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||
auto camMat = cancelOutRollAndPitch(myAvatar->getSensorToWorldMatrix() * qApp->getHMDSensorPose());
|
||||
Transform t;
|
||||
t.setTranslation(extractTranslation(camMat));
|
||||
t.setRotation(glm::quat_cast(camMat));
|
||||
qApp->getApplicationCompositor().setModelTransform(t);
|
||||
}
|
||||
} else { // other wise, if the new state is hidden/not enabled
|
||||
// alpha fadeOut the overlay mesh.
|
||||
qApp->getApplicationCompositor().fadeOut();
|
||||
|
||||
// disable mouse clicks from script
|
||||
qApp->getOverlays().disable();
|
||||
|
||||
// disable QML events
|
||||
if (toggleQmlEvents) { // I'd really rather always do this, but it looses drive state. bugzid:501
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
offscreenUi->getRootItem()->setEnabled(false);
|
||||
bool targetVisible = Menu::getInstance()->isOptionChecked(MenuOption::Overlays) && (0 == (_flags & SuppressMask));
|
||||
if (targetVisible != currentVisible) {
|
||||
offscreenUi->setPinned(!targetVisible);
|
||||
if (targetVisible && _hmdMode) {
|
||||
centerUI();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool OverlayConductor::getEnabled() const {
|
||||
return _enabled;
|
||||
}
|
||||
|
||||
|
|
|
@ -17,23 +17,31 @@ public:
|
|||
~OverlayConductor();
|
||||
|
||||
void update(float dt);
|
||||
void setEnabled(bool enable, bool toggleQmlEvents = true);
|
||||
bool getEnabled() const;
|
||||
void centerUI();
|
||||
|
||||
private:
|
||||
void updateMode();
|
||||
bool headOutsideOverlay() const;
|
||||
bool updateAvatarHasDriveInput();
|
||||
bool updateAvatarIsAtRest();
|
||||
|
||||
enum Mode {
|
||||
FLAT,
|
||||
SITTING,
|
||||
STANDING
|
||||
enum SupressionFlags {
|
||||
SuppressedByDrive = 0x01,
|
||||
SuppressedByHead = 0x02,
|
||||
SuppressMask = 0x03,
|
||||
};
|
||||
|
||||
Mode _mode { FLAT };
|
||||
bool _enabled { false };
|
||||
bool _driving { false };
|
||||
quint64 _timeInPotentialMode { 0 };
|
||||
bool _wantsOverlays { true };
|
||||
uint8_t _flags { SuppressedByDrive };
|
||||
bool _hmdMode { false };
|
||||
|
||||
// used by updateAvatarHasDriveInput
|
||||
quint64 _desiredDrivingTimer { 0 };
|
||||
bool _desiredDriving { false };
|
||||
bool _currentDriving { false };
|
||||
|
||||
// used by updateAvatarIsAtRest
|
||||
quint64 _desiredAtRestTimer { 0 };
|
||||
bool _desiredAtRest { true };
|
||||
bool _currentAtRest { true };
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -62,9 +62,9 @@ void setupPreferences() {
|
|||
preferences->addPreference(new CheckPreference(AVATAR_BASICS, "Snap turn when in HMD", getter, setter));
|
||||
}
|
||||
{
|
||||
auto getter = [=]()->bool {return myAvatar->getClearOverlayWhenDriving(); };
|
||||
auto setter = [=](bool value) { myAvatar->setClearOverlayWhenDriving(value); };
|
||||
preferences->addPreference(new CheckPreference(AVATAR_BASICS, "Clear overlays when driving", getter, setter));
|
||||
auto getter = [=]()->bool {return myAvatar->getClearOverlayWhenMoving(); };
|
||||
auto setter = [=](bool value) { myAvatar->setClearOverlayWhenMoving(value); };
|
||||
preferences->addPreference(new CheckPreference(AVATAR_BASICS, "Clear overlays when moving", getter, setter));
|
||||
}
|
||||
{
|
||||
auto getter = []()->QString { return Snapshot::snapshotsLocation.get(); };
|
||||
|
|
|
@ -44,6 +44,20 @@ bool AvatarHashMap::isAvatarInRange(const glm::vec3& position, const float range
|
|||
return false;
|
||||
}
|
||||
|
||||
int AvatarHashMap::numberOfAvatarsInRange(const glm::vec3& position, float rangeMeters) {
|
||||
auto hashCopy = getHashCopy();
|
||||
auto rangeMeters2 = rangeMeters * rangeMeters;
|
||||
int count = 0;
|
||||
for (const AvatarSharedPointer& sharedAvatar : hashCopy) {
|
||||
glm::vec3 avatarPosition = sharedAvatar->getPosition();
|
||||
auto distance2 = glm::distance2(avatarPosition, position);
|
||||
if (distance2 < rangeMeters2) {
|
||||
++count;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
AvatarSharedPointer AvatarHashMap::newSharedAvatar() {
|
||||
return std::make_shared<AvatarData>();
|
||||
}
|
||||
|
|
|
@ -39,6 +39,7 @@ public:
|
|||
Q_INVOKABLE AvatarData* getAvatar(QUuid avatarID);
|
||||
|
||||
virtual AvatarSharedPointer getAvatarBySessionID(const QUuid& sessionID) { return findAvatar(sessionID); }
|
||||
int numberOfAvatarsInRange(const glm::vec3& position, float rangeMeters);
|
||||
|
||||
signals:
|
||||
void avatarAddedEvent(const QUuid& sessionUUID);
|
||||
|
|
|
@ -32,6 +32,7 @@ class Mapping;
|
|||
using MappingPointer = std::shared_ptr<Mapping>;
|
||||
using MappingList = std::list<MappingPointer>;
|
||||
|
||||
struct Pose;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -44,7 +44,8 @@ namespace controller {
|
|||
LS_TOUCH,
|
||||
LEFT_THUMB_UP,
|
||||
LS_CENTER,
|
||||
LS_OUTER,
|
||||
LS_X,
|
||||
LS_Y,
|
||||
|
||||
RIGHT_PRIMARY_THUMB,
|
||||
RIGHT_SECONDARY_THUMB,
|
||||
|
@ -53,7 +54,8 @@ namespace controller {
|
|||
RS_TOUCH,
|
||||
RIGHT_THUMB_UP,
|
||||
RS_CENTER,
|
||||
RS_OUTER,
|
||||
RS_X,
|
||||
RS_Y,
|
||||
|
||||
LEFT_PRIMARY_INDEX,
|
||||
LEFT_SECONDARY_INDEX,
|
||||
|
|
|
@ -336,7 +336,9 @@ void CompositorHelper::computeHmdPickRay(const glm::vec2& cursorPos, glm::vec3&
|
|||
}
|
||||
|
||||
glm::mat4 CompositorHelper::getUiTransform() const {
|
||||
return _currentCamera * glm::inverse(_currentDisplayPlugin->getHeadPose());
|
||||
glm::mat4 modelMat;
|
||||
_modelTransform.getMatrix(modelMat);
|
||||
return _currentCamera * glm::inverse(_currentDisplayPlugin->getHeadPose()) * modelMat;
|
||||
}
|
||||
|
||||
//Finds the collision point of a world space ray
|
||||
|
@ -346,7 +348,7 @@ bool CompositorHelper::calculateRayUICollisionPoint(const glm::vec3& position, c
|
|||
auto relativePosition = vec3(relativePosition4) / relativePosition4.w;
|
||||
auto relativeDirection = glm::inverse(glm::quat_cast(UITransform)) * direction;
|
||||
|
||||
float uiRadius = _oculusUIRadius; // * myAvatar->getUniformScale(); // FIXME - how do we want to handle avatar scale
|
||||
float uiRadius = _hmdUIRadius; // * myAvatar->getUniformScale(); // FIXME - how do we want to handle avatar scale
|
||||
|
||||
float instersectionDistance;
|
||||
if (raySphereIntersect(relativeDirection, relativePosition, uiRadius, &instersectionDistance)){
|
||||
|
@ -407,84 +409,25 @@ void CompositorHelper::updateTooltips() {
|
|||
//}
|
||||
}
|
||||
|
||||
static const float FADE_DURATION = 500.0f;
|
||||
static const float FADE_IN_ALPHA = 1.0f;
|
||||
static const float FADE_OUT_ALPHA = 0.0f;
|
||||
|
||||
void CompositorHelper::startFadeFailsafe(float endValue) {
|
||||
_fadeStarted = usecTimestampNow();
|
||||
_fadeFailsafeEndValue = endValue;
|
||||
|
||||
const int SLIGHT_DELAY = 10;
|
||||
QTimer::singleShot(FADE_DURATION + SLIGHT_DELAY, [this]{
|
||||
checkFadeFailsafe();
|
||||
});
|
||||
}
|
||||
|
||||
void CompositorHelper::checkFadeFailsafe() {
|
||||
auto elapsedInFade = usecTimestampNow() - _fadeStarted;
|
||||
if (elapsedInFade > FADE_DURATION) {
|
||||
setAlpha(_fadeFailsafeEndValue);
|
||||
}
|
||||
}
|
||||
|
||||
void CompositorHelper::fadeIn() {
|
||||
_fadeInAlpha = true;
|
||||
|
||||
_alphaPropertyAnimation->setDuration(FADE_DURATION);
|
||||
_alphaPropertyAnimation->setStartValue(_alpha);
|
||||
_alphaPropertyAnimation->setEndValue(FADE_IN_ALPHA);
|
||||
_alphaPropertyAnimation->start();
|
||||
|
||||
// Sometimes, this "QPropertyAnimation" fails to complete the animation, and we end up with a partially faded
|
||||
// state. So we will also have this fail-safe, where we record the timestamp of the fadeRequest, and the target
|
||||
// value of the fade, and if after that time we still haven't faded all the way, we will kick it to the final
|
||||
// fade value
|
||||
startFadeFailsafe(FADE_IN_ALPHA);
|
||||
}
|
||||
|
||||
void CompositorHelper::fadeOut() {
|
||||
_fadeInAlpha = false;
|
||||
|
||||
_alphaPropertyAnimation->setDuration(FADE_DURATION);
|
||||
_alphaPropertyAnimation->setStartValue(_alpha);
|
||||
_alphaPropertyAnimation->setEndValue(FADE_OUT_ALPHA);
|
||||
_alphaPropertyAnimation->start();
|
||||
startFadeFailsafe(FADE_OUT_ALPHA);
|
||||
}
|
||||
|
||||
void CompositorHelper::toggle() {
|
||||
if (_fadeInAlpha) {
|
||||
fadeOut();
|
||||
} else {
|
||||
fadeIn();
|
||||
}
|
||||
}
|
||||
|
||||
// eyePose and headPosition are in sensor space.
|
||||
// the resulting matrix should be in view space.
|
||||
glm::mat4 CompositorHelper::getReticleTransform(const glm::mat4& eyePose, const glm::vec3& headPosition) const {
|
||||
glm::mat4 result;
|
||||
if (isHMD()) {
|
||||
vec3 reticleScale = vec3(Cursor::Manager::instance().getScale() * reticleSize);
|
||||
auto reticlePosition = getReticlePosition();
|
||||
auto spherical = overlayToSpherical(reticlePosition);
|
||||
// The pointer transform relative to the sensor
|
||||
auto pointerTransform = glm::mat4_cast(quat(vec3(-spherical.y, spherical.x, 0.0f))) * glm::translate(mat4(), vec3(0, 0, -1));
|
||||
float reticleDepth = getReticleDepth();
|
||||
if (reticleDepth != 1.0f) {
|
||||
// Cursor position in UI space
|
||||
auto cursorPosition = vec3(pointerTransform[3]) / pointerTransform[3].w;
|
||||
// Ray to the cursor, in UI space
|
||||
auto cursorRay = glm::normalize(cursorPosition - headPosition) * reticleDepth;
|
||||
// Move the ray to be relative to the head pose
|
||||
pointerTransform[3] = vec4(cursorRay + headPosition, 1);
|
||||
// Scale up the cursor because of distance
|
||||
reticleScale *= reticleDepth;
|
||||
vec2 spherical = overlayToSpherical(getReticlePosition());
|
||||
vec3 overlaySurfacePoint = getPoint(spherical.x, spherical.y); // overlay space
|
||||
vec3 sensorSurfacePoint = _modelTransform.transform(overlaySurfacePoint); // sensor space
|
||||
vec3 d = sensorSurfacePoint - headPosition;
|
||||
vec3 reticlePosition;
|
||||
if (glm::length(d) >= EPSILON) {
|
||||
d = glm::normalize(d);
|
||||
} else {
|
||||
d = glm::normalize(overlaySurfacePoint);
|
||||
}
|
||||
glm::mat4 overlayXfm;
|
||||
_modelTransform.getMatrix(overlayXfm);
|
||||
pointerTransform = overlayXfm * pointerTransform;
|
||||
pointerTransform = glm::inverse(eyePose) * pointerTransform;
|
||||
result = glm::scale(pointerTransform, reticleScale);
|
||||
reticlePosition = headPosition + (d * getReticleDepth());
|
||||
quat reticleOrientation = glm::quat_cast(_currentDisplayPlugin->getHeadPose());
|
||||
vec3 reticleScale = vec3(Cursor::Manager::instance().getScale() * reticleSize * getReticleDepth());
|
||||
return glm::inverse(eyePose) * createMatFromScaleQuatAndPos(reticleScale, reticleOrientation, reticlePosition);
|
||||
} else {
|
||||
static const float CURSOR_PIXEL_SIZE = 32.0f;
|
||||
const auto canvasSize = vec2(toGlm(_renderingWidget->size()));;
|
||||
|
|
|
@ -38,7 +38,7 @@ const float MAGNIFY_MULT = 2.0f;
|
|||
class CompositorHelper : public QObject, public Dependency {
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(float alpha READ getAlpha WRITE setAlpha)
|
||||
Q_PROPERTY(float alpha READ getAlpha WRITE setAlpha NOTIFY alphaChanged)
|
||||
Q_PROPERTY(bool reticleOverDesktop READ getReticleOverDesktop WRITE setReticleOverDesktop)
|
||||
public:
|
||||
static const uvec2 VIRTUAL_SCREEN_SIZE;
|
||||
|
@ -75,12 +75,8 @@ public:
|
|||
void setModelTransform(const Transform& transform) { _modelTransform = transform; }
|
||||
const Transform& getModelTransform() const { return _modelTransform; }
|
||||
|
||||
void fadeIn();
|
||||
void fadeOut();
|
||||
void toggle();
|
||||
|
||||
float getAlpha() const { return _alpha; }
|
||||
void setAlpha(float alpha) { _alpha = alpha; }
|
||||
void setAlpha(float alpha) { if (alpha != _alpha) { emit alphaChanged(); _alpha = alpha; } }
|
||||
|
||||
bool getReticleVisible() const { return _reticleVisible; }
|
||||
void setReticleVisible(bool visible) { _reticleVisible = visible; }
|
||||
|
@ -113,10 +109,11 @@ public:
|
|||
void setReticleOverDesktop(bool value) { _isOverDesktop = value; }
|
||||
|
||||
void setDisplayPlugin(const DisplayPluginPointer& displayPlugin) { _currentDisplayPlugin = displayPlugin; }
|
||||
void setFrameInfo(uint32_t frame, const glm::mat4& camera) { _currentCamera = camera; _currentFrame = frame; }
|
||||
void setFrameInfo(uint32_t frame, const glm::mat4& camera) { _currentCamera = camera; }
|
||||
|
||||
signals:
|
||||
void allowMouseCaptureChanged();
|
||||
void alphaChanged();
|
||||
|
||||
protected slots:
|
||||
void sendFakeMouseEvent();
|
||||
|
@ -127,7 +124,6 @@ private:
|
|||
|
||||
DisplayPluginPointer _currentDisplayPlugin;
|
||||
glm::mat4 _currentCamera;
|
||||
uint32_t _currentFrame { 0 };
|
||||
QWidget* _renderingWidget{ nullptr };
|
||||
|
||||
//// Support for hovering and tooltips
|
||||
|
@ -144,16 +140,7 @@ private:
|
|||
float _textureAspectRatio { VIRTUAL_UI_ASPECT_RATIO };
|
||||
|
||||
float _alpha { 1.0f };
|
||||
float _prevAlpha { 1.0f };
|
||||
float _fadeInAlpha { true };
|
||||
float _oculusUIRadius { 1.0f };
|
||||
|
||||
quint64 _fadeStarted { 0 };
|
||||
float _fadeFailsafeEndValue { 1.0f };
|
||||
void checkFadeFailsafe();
|
||||
void startFadeFailsafe(float endValue);
|
||||
|
||||
int _reticleQuad;
|
||||
float _hmdUIRadius { 1.0f };
|
||||
|
||||
int _previousBorderWidth { -1 };
|
||||
int _previousBorderHeight { -1 };
|
||||
|
|
|
@ -215,9 +215,10 @@ OpenGLDisplayPlugin::OpenGLDisplayPlugin() {
|
|||
}
|
||||
|
||||
void OpenGLDisplayPlugin::cleanupForSceneTexture(const gpu::TexturePointer& sceneTexture) {
|
||||
Lock lock(_mutex);
|
||||
Q_ASSERT(_sceneTextureToFrameIndexMap.contains(sceneTexture));
|
||||
_sceneTextureToFrameIndexMap.remove(sceneTexture);
|
||||
withRenderThreadLock([&] {
|
||||
Q_ASSERT(_sceneTextureToFrameIndexMap.contains(sceneTexture));
|
||||
_sceneTextureToFrameIndexMap.remove(sceneTexture);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
@ -275,6 +276,15 @@ bool OpenGLDisplayPlugin::activate() {
|
|||
_container->makeRenderingContextCurrent();
|
||||
#endif
|
||||
|
||||
auto compositorHelper = DependencyManager::get<CompositorHelper>();
|
||||
connect(compositorHelper.data(), &CompositorHelper::alphaChanged, [this] {
|
||||
auto compositorHelper = DependencyManager::get<CompositorHelper>();
|
||||
auto animation = new QPropertyAnimation(this, "overlayAlpha");
|
||||
animation->setDuration(200);
|
||||
animation->setEndValue(compositorHelper->getAlpha());
|
||||
animation->start();
|
||||
});
|
||||
|
||||
if (isHmd() && (getHmdScreen() >= 0)) {
|
||||
_container->showDisplayPluginsTools();
|
||||
}
|
||||
|
@ -284,6 +294,9 @@ bool OpenGLDisplayPlugin::activate() {
|
|||
|
||||
void OpenGLDisplayPlugin::deactivate() {
|
||||
|
||||
auto compositorHelper = DependencyManager::get<CompositorHelper>();
|
||||
disconnect(compositorHelper.data());
|
||||
|
||||
#if THREADED_PRESENT
|
||||
auto presentThread = DependencyManager::get<PresentThread>();
|
||||
// Does not return until the GL transition has completeed
|
||||
|
@ -410,10 +423,9 @@ void OpenGLDisplayPlugin::submitSceneTexture(uint32_t frameIndex, const gpu::Tex
|
|||
return;
|
||||
}
|
||||
|
||||
{
|
||||
Lock lock(_mutex);
|
||||
withRenderThreadLock([&] {
|
||||
_sceneTextureToFrameIndexMap[sceneTexture] = frameIndex;
|
||||
}
|
||||
});
|
||||
|
||||
// Submit it to the presentation thread via escrow
|
||||
_sceneTextureEscrow.submit(sceneTexture);
|
||||
|
@ -447,11 +459,12 @@ void OpenGLDisplayPlugin::updateTextures() {
|
|||
}
|
||||
|
||||
void OpenGLDisplayPlugin::updateFrameData() {
|
||||
Lock lock(_mutex);
|
||||
auto previousFrameIndex = _currentPresentFrameIndex;
|
||||
_currentPresentFrameIndex = _sceneTextureToFrameIndexMap[_currentSceneTexture];
|
||||
auto skippedCount = (_currentPresentFrameIndex - previousFrameIndex) - 1;
|
||||
_droppedFrameRate.increment(skippedCount);
|
||||
withPresentThreadLock([&] {
|
||||
auto previousFrameIndex = _currentPresentFrameIndex;
|
||||
_currentPresentFrameIndex = _sceneTextureToFrameIndexMap[_currentSceneTexture];
|
||||
auto skippedCount = (_currentPresentFrameIndex - previousFrameIndex) - 1;
|
||||
_droppedFrameRate.increment(skippedCount);
|
||||
});
|
||||
}
|
||||
|
||||
void OpenGLDisplayPlugin::compositeOverlay() {
|
||||
|
@ -460,25 +473,22 @@ void OpenGLDisplayPlugin::compositeOverlay() {
|
|||
auto compositorHelper = DependencyManager::get<CompositorHelper>();
|
||||
|
||||
useProgram(_program);
|
||||
// set the alpha
|
||||
Uniform<float>(*_program, _alphaUniform).Set(_compositeOverlayAlpha);
|
||||
// check the alpha
|
||||
auto overlayAlpha = compositorHelper->getAlpha();
|
||||
if (overlayAlpha > 0.0f) {
|
||||
// set the alpha
|
||||
Uniform<float>(*_program, _alphaUniform).Set(overlayAlpha);
|
||||
|
||||
// Overlay draw
|
||||
if (isStereo()) {
|
||||
Uniform<glm::mat4>(*_program, _mvpUniform).Set(mat4());
|
||||
for_each_eye([&](Eye eye) {
|
||||
eyeViewport(eye);
|
||||
drawUnitQuad();
|
||||
});
|
||||
} else {
|
||||
// Overlay draw
|
||||
Uniform<glm::mat4>(*_program, _mvpUniform).Set(mat4());
|
||||
// Overlay draw
|
||||
if (isStereo()) {
|
||||
Uniform<glm::mat4>(*_program, _mvpUniform).Set(mat4());
|
||||
for_each_eye([&](Eye eye) {
|
||||
eyeViewport(eye);
|
||||
drawUnitQuad();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Overlay draw
|
||||
Uniform<glm::mat4>(*_program, _mvpUniform).Set(mat4());
|
||||
drawUnitQuad();
|
||||
}
|
||||
// restore the alpha
|
||||
Uniform<float>(*_program, _alphaUniform).Set(1.0);
|
||||
}
|
||||
|
||||
|
@ -487,23 +497,19 @@ void OpenGLDisplayPlugin::compositePointer() {
|
|||
auto compositorHelper = DependencyManager::get<CompositorHelper>();
|
||||
|
||||
useProgram(_program);
|
||||
// check the alpha
|
||||
auto overlayAlpha = compositorHelper->getAlpha();
|
||||
if (overlayAlpha > 0.0f) {
|
||||
// set the alpha
|
||||
Uniform<float>(*_program, _alphaUniform).Set(overlayAlpha);
|
||||
|
||||
Uniform<glm::mat4>(*_program, _mvpUniform).Set(compositorHelper->getReticleTransform(glm::mat4()));
|
||||
if (isStereo()) {
|
||||
for_each_eye([&](Eye eye) {
|
||||
eyeViewport(eye);
|
||||
drawUnitQuad();
|
||||
});
|
||||
} else {
|
||||
// set the alpha
|
||||
Uniform<float>(*_program, _alphaUniform).Set(_compositeOverlayAlpha);
|
||||
Uniform<glm::mat4>(*_program, _mvpUniform).Set(compositorHelper->getReticleTransform(glm::mat4()));
|
||||
if (isStereo()) {
|
||||
for_each_eye([&](Eye eye) {
|
||||
eyeViewport(eye);
|
||||
drawUnitQuad();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
drawUnitQuad();
|
||||
}
|
||||
Uniform<glm::mat4>(*_program, _mvpUniform).Set(mat4());
|
||||
// restore the alpha
|
||||
Uniform<float>(*_program, _alphaUniform).Set(1.0);
|
||||
}
|
||||
|
||||
|
@ -523,14 +529,14 @@ void OpenGLDisplayPlugin::compositeLayers() {
|
|||
}
|
||||
_compositeFramebuffer->Bound(Framebuffer::Target::Draw, [&] {
|
||||
Context::Viewport(targetRenderSize.x, targetRenderSize.y);
|
||||
Context::Clear().DepthBuffer();
|
||||
glBindTexture(GL_TEXTURE_2D, getSceneTextureId());
|
||||
compositeScene();
|
||||
auto sceneTextureId = getSceneTextureId();
|
||||
auto overlayTextureId = getOverlayTextureId();
|
||||
glBindTexture(GL_TEXTURE_2D, sceneTextureId);
|
||||
compositeScene();
|
||||
if (overlayTextureId) {
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glBindTexture(GL_TEXTURE_2D, overlayTextureId);
|
||||
Context::Enable(Capability::Blend);
|
||||
Context::BlendFunc(BlendFunction::SrcAlpha, BlendFunction::OneMinusSrcAlpha);
|
||||
compositeOverlay();
|
||||
|
||||
auto compositorHelper = DependencyManager::get<CompositorHelper>();
|
||||
|
@ -538,11 +544,16 @@ void OpenGLDisplayPlugin::compositeLayers() {
|
|||
auto& cursorManager = Cursor::Manager::instance();
|
||||
const auto& cursorData = _cursorsData[cursorManager.getCursor()->getIcon()];
|
||||
glBindTexture(GL_TEXTURE_2D, cursorData.texture);
|
||||
glActiveTexture(GL_TEXTURE1);
|
||||
glBindTexture(GL_TEXTURE_2D, overlayTextureId);
|
||||
compositePointer();
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
}
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glDisable(GL_BLEND);
|
||||
Context::Disable(Capability::Blend);
|
||||
}
|
||||
compositeExtra();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -580,7 +591,11 @@ float OpenGLDisplayPlugin::newFramePresentRate() const {
|
|||
}
|
||||
|
||||
float OpenGLDisplayPlugin::droppedFrameRate() const {
|
||||
return _droppedFrameRate.rate();
|
||||
float result;
|
||||
withRenderThreadLock([&] {
|
||||
result = _droppedFrameRate.rate();
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
float OpenGLDisplayPlugin::presentRate() const {
|
||||
|
@ -695,3 +710,18 @@ void OpenGLDisplayPlugin::useProgram(const ProgramPtr& program) {
|
|||
_activeProgram = program;
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLDisplayPlugin::assertIsRenderThread() const {
|
||||
Q_ASSERT(QThread::currentThread() != _presentThread);
|
||||
}
|
||||
|
||||
void OpenGLDisplayPlugin::assertIsPresentThread() const {
|
||||
Q_ASSERT(QThread::currentThread() == _presentThread);
|
||||
}
|
||||
|
||||
bool OpenGLDisplayPlugin::beginFrameRender(uint32_t frameIndex) {
|
||||
withRenderThreadLock([&] {
|
||||
_compositeOverlayAlpha = _overlayAlpha;
|
||||
});
|
||||
return Parent::beginFrameRender(frameIndex);
|
||||
}
|
||||
|
|
|
@ -24,7 +24,9 @@
|
|||
#define THREADED_PRESENT 1
|
||||
|
||||
class OpenGLDisplayPlugin : public DisplayPlugin {
|
||||
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(float overlayAlpha MEMBER _overlayAlpha)
|
||||
using Parent = DisplayPlugin;
|
||||
protected:
|
||||
using Mutex = std::mutex;
|
||||
using Lock = std::unique_lock<Mutex>;
|
||||
|
@ -61,6 +63,7 @@ public:
|
|||
|
||||
float droppedFrameRate() const override;
|
||||
|
||||
bool beginFrameRender(uint32_t frameIndex) override;
|
||||
protected:
|
||||
#if THREADED_PRESENT
|
||||
friend class PresentThread;
|
||||
|
@ -75,6 +78,7 @@ protected:
|
|||
virtual void compositeScene();
|
||||
virtual void compositeOverlay();
|
||||
virtual void compositePointer();
|
||||
virtual void compositeExtra() {};
|
||||
|
||||
virtual bool hasFocus() const override;
|
||||
|
||||
|
@ -110,12 +114,12 @@ protected:
|
|||
int32_t _alphaUniform { -1 };
|
||||
ShapeWrapperPtr _plane;
|
||||
|
||||
mutable Mutex _mutex;
|
||||
RateCounter<> _droppedFrameRate;
|
||||
RateCounter<> _newFrameRate;
|
||||
RateCounter<> _presentRate;
|
||||
QMap<gpu::TexturePointer, uint32_t> _sceneTextureToFrameIndexMap;
|
||||
uint32_t _currentPresentFrameIndex { 0 };
|
||||
float _compositeOverlayAlpha{ 1.0f };
|
||||
|
||||
gpu::TexturePointer _currentSceneTexture;
|
||||
gpu::TexturePointer _currentOverlayTexture;
|
||||
|
@ -136,10 +140,28 @@ protected:
|
|||
BasicFramebufferWrapperPtr _compositeFramebuffer;
|
||||
bool _lockCurrentTexture { false };
|
||||
|
||||
void assertIsRenderThread() const;
|
||||
void assertIsPresentThread() const;
|
||||
|
||||
template<typename F>
|
||||
void withPresentThreadLock(F f) const {
|
||||
assertIsPresentThread();
|
||||
Lock lock(_presentMutex);
|
||||
f();
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
void withRenderThreadLock(F f) const {
|
||||
assertIsRenderThread();
|
||||
Lock lock(_presentMutex);
|
||||
f();
|
||||
}
|
||||
|
||||
private:
|
||||
using Parent = DisplayPlugin;
|
||||
// Any resource shared by the main thread and the presentation thread must
|
||||
// be serialized through this mutex
|
||||
mutable Mutex _presentMutex;
|
||||
ProgramPtr _activeProgram;
|
||||
float _overlayAlpha{ 1.0f };
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
#include <memory>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtx/intersect.hpp>
|
||||
|
||||
#include <QtCore/QLoggingCategory>
|
||||
#include <QtWidgets/QApplication>
|
||||
|
@ -37,7 +38,6 @@ QRect HmdDisplayPlugin::getRecommendedOverlayRect() const {
|
|||
return CompositorHelper::VIRTUAL_SCREEN_RECOMMENDED_OVERLAY_RECT;
|
||||
}
|
||||
|
||||
|
||||
bool HmdDisplayPlugin::internalActivate() {
|
||||
_monoPreview = _container->getBoolSetting("monoPreview", DEFAULT_MONO_VIEW);
|
||||
|
||||
|
@ -197,14 +197,43 @@ static ProgramPtr getReprojectionProgram() {
|
|||
#endif
|
||||
|
||||
|
||||
static const char * LASER_VS = R"VS(#version 410 core
|
||||
uniform mat4 mvp = mat4(1);
|
||||
|
||||
in vec3 Position;
|
||||
|
||||
out vec3 vPosition;
|
||||
|
||||
void main() {
|
||||
gl_Position = mvp * vec4(Position, 1);
|
||||
vPosition = Position;
|
||||
}
|
||||
|
||||
)VS";
|
||||
|
||||
static const char * LASER_FS = R"FS(#version 410 core
|
||||
|
||||
uniform vec4 color = vec4(1.0, 1.0, 1.0, 1.0);
|
||||
in vec3 vPosition;
|
||||
|
||||
out vec4 FragColor;
|
||||
|
||||
void main() {
|
||||
FragColor = color;
|
||||
}
|
||||
|
||||
)FS";
|
||||
|
||||
void HmdDisplayPlugin::customizeContext() {
|
||||
Parent::customizeContext();
|
||||
// Only enable mirroring if we know vsync is disabled
|
||||
enableVsync(false);
|
||||
_enablePreview = !isVsyncEnabled();
|
||||
_sphereSection = loadSphereSection(_program, CompositorHelper::VIRTUAL_UI_TARGET_FOV.y, CompositorHelper::VIRTUAL_UI_ASPECT_RATIO);
|
||||
compileProgram(_laserProgram, LASER_VS, LASER_FS);
|
||||
_laserGeometry = loadLaser(_laserProgram);
|
||||
compileProgram(_reprojectionProgram, REPROJECTION_VS, REPROJECTION_FS);
|
||||
|
||||
|
||||
using namespace oglplus;
|
||||
REPROJECTION_MATRIX_LOCATION = Uniform<glm::mat3>(*_reprojectionProgram, "reprojection").Location();
|
||||
INVERSE_PROJECTION_MATRIX_LOCATION = Uniform<glm::mat4>(*_reprojectionProgram, "inverseProjections").Location();
|
||||
|
@ -215,6 +244,8 @@ void HmdDisplayPlugin::uncustomizeContext() {
|
|||
_sphereSection.reset();
|
||||
_compositeFramebuffer.reset();
|
||||
_reprojectionProgram.reset();
|
||||
_laserProgram.reset();
|
||||
_laserGeometry.reset();
|
||||
Parent::uncustomizeContext();
|
||||
}
|
||||
|
||||
|
@ -253,23 +284,20 @@ void HmdDisplayPlugin::compositeScene() {
|
|||
void HmdDisplayPlugin::compositeOverlay() {
|
||||
using namespace oglplus;
|
||||
auto compositorHelper = DependencyManager::get<CompositorHelper>();
|
||||
glm::mat4 modelMat = compositorHelper->getModelTransform().getMatrix();
|
||||
|
||||
// check the alpha
|
||||
useProgram(_program);
|
||||
auto overlayAlpha = compositorHelper->getAlpha();
|
||||
if (overlayAlpha > 0.0f) {
|
||||
// set the alpha
|
||||
Uniform<float>(*_program, _alphaUniform).Set(overlayAlpha);
|
||||
|
||||
_sphereSection->Use();
|
||||
for_each_eye([&](Eye eye) {
|
||||
eyeViewport(eye);
|
||||
auto modelView = glm::inverse(_currentPresentFrameInfo.presentPose * getEyeToHeadTransform(eye));
|
||||
auto mvp = _eyeProjections[eye] * modelView;
|
||||
Uniform<glm::mat4>(*_program, _mvpUniform).Set(mvp);
|
||||
_sphereSection->Draw();
|
||||
});
|
||||
}
|
||||
// set the alpha
|
||||
Uniform<float>(*_program, _alphaUniform).Set(_compositeOverlayAlpha);
|
||||
_sphereSection->Use();
|
||||
for_each_eye([&](Eye eye) {
|
||||
eyeViewport(eye);
|
||||
auto modelView = glm::inverse(_currentPresentFrameInfo.presentPose * getEyeToHeadTransform(eye)) * modelMat;
|
||||
auto mvp = _eyeProjections[eye] * modelView;
|
||||
Uniform<glm::mat4>(*_program, _mvpUniform).Set(mvp);
|
||||
_sphereSection->Draw();
|
||||
});
|
||||
// restore the alpha
|
||||
Uniform<float>(*_program, _alphaUniform).Set(1.0);
|
||||
}
|
||||
|
||||
|
@ -278,29 +306,27 @@ void HmdDisplayPlugin::compositePointer() {
|
|||
|
||||
auto compositorHelper = DependencyManager::get<CompositorHelper>();
|
||||
|
||||
// check the alpha
|
||||
useProgram(_program);
|
||||
auto overlayAlpha = compositorHelper->getAlpha();
|
||||
if (overlayAlpha > 0.0f) {
|
||||
// set the alpha
|
||||
Uniform<float>(*_program, _alphaUniform).Set(overlayAlpha);
|
||||
// set the alpha
|
||||
Uniform<float>(*_program, _alphaUniform).Set(_compositeOverlayAlpha);
|
||||
|
||||
// Mouse pointer
|
||||
_plane->Use();
|
||||
// Reconstruct the headpose from the eye poses
|
||||
auto headPosition = vec3(_currentPresentFrameInfo.presentPose[3]);
|
||||
for_each_eye([&](Eye eye) {
|
||||
eyeViewport(eye);
|
||||
auto eyePose = _currentPresentFrameInfo.presentPose * getEyeToHeadTransform(eye);
|
||||
auto reticleTransform = compositorHelper->getReticleTransform(eyePose, headPosition);
|
||||
auto mvp = _eyeProjections[eye] * reticleTransform;
|
||||
Uniform<glm::mat4>(*_program, _mvpUniform).Set(mvp);
|
||||
_plane->Draw();
|
||||
});
|
||||
}
|
||||
// Mouse pointer
|
||||
_plane->Use();
|
||||
// Reconstruct the headpose from the eye poses
|
||||
auto headPosition = vec3(_currentPresentFrameInfo.presentPose[3]);
|
||||
for_each_eye([&](Eye eye) {
|
||||
eyeViewport(eye);
|
||||
auto eyePose = _currentPresentFrameInfo.presentPose * getEyeToHeadTransform(eye);
|
||||
auto reticleTransform = compositorHelper->getReticleTransform(eyePose, headPosition);
|
||||
auto mvp = _eyeProjections[eye] * reticleTransform;
|
||||
Uniform<glm::mat4>(*_program, _mvpUniform).Set(mvp);
|
||||
_plane->Draw();
|
||||
});
|
||||
// restore the alpha
|
||||
Uniform<float>(*_program, _alphaUniform).Set(1.0);
|
||||
}
|
||||
|
||||
|
||||
void HmdDisplayPlugin::internalPresent() {
|
||||
|
||||
PROFILE_RANGE_EX(__FUNCTION__, 0xff00ff00, (uint64_t)presentCount())
|
||||
|
@ -357,22 +383,117 @@ void HmdDisplayPlugin::setEyeRenderPose(uint32_t frameIndex, Eye eye, const glm:
|
|||
|
||||
void HmdDisplayPlugin::updateFrameData() {
|
||||
// Check if we have old frame data to discard
|
||||
{
|
||||
Lock lock(_mutex);
|
||||
withPresentThreadLock([&] {
|
||||
auto itr = _frameInfos.find(_currentPresentFrameIndex);
|
||||
if (itr != _frameInfos.end()) {
|
||||
_frameInfos.erase(itr);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Parent::updateFrameData();
|
||||
|
||||
{
|
||||
Lock lock(_mutex);
|
||||
withPresentThreadLock([&] {
|
||||
_currentPresentFrameInfo = _frameInfos[_currentPresentFrameIndex];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
glm::mat4 HmdDisplayPlugin::getHeadPose() const {
|
||||
return _currentRenderFrameInfo.renderPose;
|
||||
}
|
||||
|
||||
bool HmdDisplayPlugin::setHandLaser(uint32_t hands, HandLaserMode mode, const vec4& color, const vec3& direction) {
|
||||
HandLaserInfo info;
|
||||
info.mode = mode;
|
||||
info.color = color;
|
||||
info.direction = direction;
|
||||
withRenderThreadLock([&] {
|
||||
if (hands & Hand::LeftHand) {
|
||||
_handLasers[0] = info;
|
||||
}
|
||||
if (hands & Hand::RightHand) {
|
||||
_handLasers[1] = info;
|
||||
}
|
||||
});
|
||||
// FIXME defer to a child class plugin to determine if hand lasers are actually
|
||||
// available based on the presence or absence of hand controllers
|
||||
return true;
|
||||
}
|
||||
|
||||
void HmdDisplayPlugin::compositeExtra() {
|
||||
const int NUMBER_OF_HANDS = 2;
|
||||
std::array<HandLaserInfo, NUMBER_OF_HANDS> handLasers;
|
||||
std::array<mat4, NUMBER_OF_HANDS> renderHandPoses;
|
||||
Transform uiModelTransform;
|
||||
withPresentThreadLock([&] {
|
||||
handLasers = _handLasers;
|
||||
renderHandPoses = _handPoses;
|
||||
uiModelTransform = _uiModelTransform;
|
||||
});
|
||||
|
||||
// If neither hand laser is activated, exit
|
||||
if (!handLasers[0].valid() && !handLasers[1].valid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
static const glm::mat4 identity;
|
||||
if (renderHandPoses[0] == identity && renderHandPoses[1] == identity) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Render hand lasers
|
||||
using namespace oglplus;
|
||||
useProgram(_laserProgram);
|
||||
_laserGeometry->Use();
|
||||
std::array<mat4, NUMBER_OF_HANDS> handLaserModelMatrices;
|
||||
|
||||
for (int i = 0; i < NUMBER_OF_HANDS; ++i) {
|
||||
if (renderHandPoses[i] == identity) {
|
||||
continue;
|
||||
}
|
||||
const auto& handLaser = handLasers[i];
|
||||
if (!handLaser.valid()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& laserDirection = handLaser.direction;
|
||||
auto model = renderHandPoses[i];
|
||||
auto castDirection = glm::quat_cast(model) * laserDirection;
|
||||
if (glm::abs(glm::length2(castDirection) - 1.0f) > EPSILON) {
|
||||
castDirection = glm::normalize(castDirection);
|
||||
}
|
||||
|
||||
// FIXME fetch the actual UI radius from... somewhere?
|
||||
float uiRadius = 1.0f;
|
||||
|
||||
// Find the intersection of the laser with he UI and use it to scale the model matrix
|
||||
float distance;
|
||||
if (!glm::intersectRaySphere(vec3(renderHandPoses[i][3]), castDirection, uiModelTransform.getTranslation(), uiRadius * uiRadius, distance)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Make sure we rotate to match the desired laser direction
|
||||
if (laserDirection != Vectors::UNIT_NEG_Z) {
|
||||
auto rotation = glm::rotation(Vectors::UNIT_NEG_Z, laserDirection);
|
||||
model = model * glm::mat4_cast(rotation);
|
||||
}
|
||||
|
||||
model = glm::scale(model, vec3(distance));
|
||||
handLaserModelMatrices[i] = model;
|
||||
}
|
||||
|
||||
for_each_eye([&](Eye eye) {
|
||||
eyeViewport(eye);
|
||||
auto eyePose = _currentPresentFrameInfo.presentPose * getEyeToHeadTransform(eye);
|
||||
auto view = glm::inverse(eyePose);
|
||||
const auto& projection = _eyeProjections[eye];
|
||||
for (int i = 0; i < NUMBER_OF_HANDS; ++i) {
|
||||
if (handLaserModelMatrices[i] == identity) {
|
||||
continue;
|
||||
}
|
||||
Uniform<glm::mat4>(*_laserProgram, "mvp").Set(projection * view * handLaserModelMatrices[i]);
|
||||
Uniform<glm::vec4>(*_laserProgram, "color").Set(handLasers[i].color);
|
||||
_laserGeometry->Draw();
|
||||
// TODO render some kind of visual indicator at the intersection point with the UI.
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include <ThreadSafeValueCache.h>
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <Transform.h>
|
||||
|
||||
#include "../OpenGLDisplayPlugin.h"
|
||||
|
||||
|
@ -30,7 +31,7 @@ public:
|
|||
|
||||
virtual glm::mat4 getHeadPose() const override;
|
||||
|
||||
|
||||
bool setHandLaser(uint32_t hands, HandLaserMode mode, const vec4& color, const vec3& direction) override;
|
||||
|
||||
protected:
|
||||
virtual void hmdPresent() = 0;
|
||||
|
@ -46,7 +47,22 @@ protected:
|
|||
void customizeContext() override;
|
||||
void uncustomizeContext() override;
|
||||
void updateFrameData() override;
|
||||
void compositeExtra() override;
|
||||
|
||||
struct HandLaserInfo {
|
||||
HandLaserMode mode { HandLaserMode::None };
|
||||
vec4 color { 1.0f };
|
||||
vec3 direction { 0, 0, -1 };
|
||||
|
||||
// Is this hand laser info suitable for drawing?
|
||||
bool valid() const {
|
||||
return (mode != HandLaserMode::None && color.a > 0.0f && direction != vec3());
|
||||
}
|
||||
};
|
||||
|
||||
Transform _uiModelTransform;
|
||||
std::array<HandLaserInfo, 2> _handLasers;
|
||||
std::array<glm::mat4, 2> _handPoses;
|
||||
std::array<glm::mat4, 2> _eyeOffsets;
|
||||
std::array<glm::mat4, 2> _eyeProjections;
|
||||
std::array<glm::mat4, 2> _eyeInverseProjections;
|
||||
|
@ -75,5 +91,7 @@ private:
|
|||
bool _enableReprojection { true };
|
||||
ShapeWrapperPtr _sphereSection;
|
||||
ProgramPtr _reprojectionProgram;
|
||||
ProgramPtr _laserProgram;
|
||||
ShapeWrapperPtr _laserGeometry;
|
||||
};
|
||||
|
||||
|
|
|
@ -593,23 +593,25 @@ bool RenderableModelEntityItem::isReadyToComputeShape() {
|
|||
|
||||
// the model is still being downloaded.
|
||||
return false;
|
||||
} else if (type == SHAPE_TYPE_STATIC_MESH) {
|
||||
return (_model && _model->isLoaded());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
|
||||
ShapeType type = getShapeType();
|
||||
glm::vec3 dimensions = getDimensions();
|
||||
if (type == SHAPE_TYPE_COMPOUND) {
|
||||
updateModelBounds();
|
||||
|
||||
// should never fall in here when collision model not fully loaded
|
||||
// hence we assert that all geometries exist and are loaded
|
||||
assert(_model->isLoaded() && _model->isCollisionLoaded());
|
||||
const FBXGeometry& renderGeometry = _model->getFBXGeometry();
|
||||
const FBXGeometry& collisionGeometry = _model->getCollisionFBXGeometry();
|
||||
|
||||
QVector<QVector<glm::vec3>>& points = info.getPoints();
|
||||
points.clear();
|
||||
ShapeInfo::PointCollection& pointCollection = info.getPointCollection();
|
||||
pointCollection.clear();
|
||||
uint32_t i = 0;
|
||||
|
||||
// the way OBJ files get read, each section under a "g" line is its own meshPart. We only expect
|
||||
|
@ -619,8 +621,8 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
|
|||
foreach (const FBXMesh& mesh, collisionGeometry.meshes) {
|
||||
// each meshPart is a convex hull
|
||||
foreach (const FBXMeshPart &meshPart, mesh.parts) {
|
||||
points.push_back(QVector<glm::vec3>());
|
||||
QVector<glm::vec3>& pointsInPart = points[i];
|
||||
pointCollection.push_back(QVector<glm::vec3>());
|
||||
ShapeInfo::PointList& pointsInPart = pointCollection[i];
|
||||
|
||||
// run through all the triangles and (uniquely) add each point to the hull
|
||||
uint32_t numIndices = (uint32_t)meshPart.triangleIndices.size();
|
||||
|
@ -664,7 +666,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
|
|||
|
||||
if (pointsInPart.size() == 0) {
|
||||
qCDebug(entitiesrenderer) << "Warning -- meshPart has no faces";
|
||||
points.pop_back();
|
||||
pointCollection.pop_back();
|
||||
continue;
|
||||
}
|
||||
++i;
|
||||
|
@ -677,29 +679,136 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
|
|||
// to the visual model and apply them to the collision model (without regard for the
|
||||
// collision model's extents).
|
||||
|
||||
glm::vec3 scale = getDimensions() / renderGeometry.getUnscaledMeshExtents().size();
|
||||
glm::vec3 scaleToFit = dimensions / _model->getFBXGeometry().getUnscaledMeshExtents().size();
|
||||
// multiply each point by scale before handing the point-set off to the physics engine.
|
||||
// also determine the extents of the collision model.
|
||||
AABox box;
|
||||
for (int i = 0; i < points.size(); i++) {
|
||||
for (int j = 0; j < points[i].size(); j++) {
|
||||
for (int i = 0; i < pointCollection.size(); i++) {
|
||||
for (int j = 0; j < pointCollection[i].size(); j++) {
|
||||
// compensate for registration
|
||||
points[i][j] += _model->getOffset();
|
||||
pointCollection[i][j] += _model->getOffset();
|
||||
// scale so the collision points match the model points
|
||||
points[i][j] *= scale;
|
||||
// this next subtraction is done so we can give info the offset, which will cause
|
||||
// the shape-key to change.
|
||||
points[i][j] -= _model->getOffset();
|
||||
box += points[i][j];
|
||||
pointCollection[i][j] *= scaleToFit;
|
||||
}
|
||||
}
|
||||
info.setParams(type, dimensions, _compoundShapeURL);
|
||||
} else if (type == SHAPE_TYPE_STATIC_MESH) {
|
||||
// compute meshPart local transforms
|
||||
QVector<glm::mat4> localTransforms;
|
||||
const FBXGeometry& geometry = _model->getFBXGeometry();
|
||||
int numberOfMeshes = geometry.meshes.size();
|
||||
for (int i = 0; i < numberOfMeshes; i++) {
|
||||
const FBXMesh& mesh = geometry.meshes.at(i);
|
||||
if (mesh.clusters.size() > 0) {
|
||||
const FBXCluster& cluster = mesh.clusters.at(0);
|
||||
auto jointMatrix = _model->getRig()->getJointTransform(cluster.jointIndex);
|
||||
localTransforms.push_back(jointMatrix * cluster.inverseBindMatrix);
|
||||
} else {
|
||||
glm::mat4 identity;
|
||||
localTransforms.push_back(identity);
|
||||
}
|
||||
}
|
||||
|
||||
glm::vec3 collisionModelDimensions = box.getDimensions();
|
||||
info.setParams(type, collisionModelDimensions, _compoundShapeURL);
|
||||
info.setOffset(_model->getOffset());
|
||||
updateModelBounds();
|
||||
|
||||
// should never fall in here when collision model not fully loaded
|
||||
assert(_model->isLoaded());
|
||||
|
||||
ShapeInfo::PointCollection& pointCollection = info.getPointCollection();
|
||||
pointCollection.clear();
|
||||
|
||||
ShapeInfo::PointList points;
|
||||
ShapeInfo::TriangleIndices& triangleIndices = info.getTriangleIndices();
|
||||
auto& meshes = _model->getGeometry()->getGeometry()->getMeshes();
|
||||
|
||||
Extents extents;
|
||||
int meshCount = 0;
|
||||
for (auto& mesh : meshes) {
|
||||
const gpu::BufferView& vertices = mesh->getVertexBuffer();
|
||||
const gpu::BufferView& indices = mesh->getIndexBuffer();
|
||||
const gpu::BufferView& parts = mesh->getPartBuffer();
|
||||
|
||||
// copy points
|
||||
const glm::mat4& localTransform = localTransforms[meshCount];
|
||||
uint32_t meshIndexOffset = (uint32_t)points.size();
|
||||
gpu::BufferView::Iterator<const glm::vec3> vertexItr = vertices.cbegin<const glm::vec3>();
|
||||
points.reserve((int32_t)((gpu::Size)points.size() + vertices.getNumElements()));
|
||||
while (vertexItr != vertices.cend<const glm::vec3>()) {
|
||||
glm::vec3 point = extractTranslation(localTransform * glm::translate(*vertexItr));
|
||||
points.push_back(point);
|
||||
extents.addPoint(point);
|
||||
++vertexItr;
|
||||
}
|
||||
|
||||
// copy triangleIndices
|
||||
triangleIndices.reserve((int32_t)((gpu::Size)(triangleIndices.size()) + indices.getNumElements()));
|
||||
gpu::BufferView::Iterator<const model::Mesh::Part> partItr = parts.cbegin<const model::Mesh::Part>();
|
||||
while (partItr != parts.cend<const model::Mesh::Part>()) {
|
||||
|
||||
if (partItr->_topology == model::Mesh::TRIANGLES) {
|
||||
assert(partItr->_numIndices % 3 == 0);
|
||||
auto indexItr = indices.cbegin<const gpu::BufferView::Index>() + partItr->_startIndex;
|
||||
auto indexEnd = indexItr + partItr->_numIndices;
|
||||
while (indexItr != indexEnd) {
|
||||
triangleIndices.push_back(*indexItr + meshIndexOffset);
|
||||
++indexItr;
|
||||
}
|
||||
} else if (partItr->_topology == model::Mesh::TRIANGLE_STRIP) {
|
||||
assert(partItr->_numIndices > 2);
|
||||
uint32_t approxNumIndices = 3 * partItr->_numIndices;
|
||||
if (approxNumIndices > (uint32_t)(triangleIndices.capacity() - triangleIndices.size())) {
|
||||
// we underestimated the final size of triangleIndices so we pre-emptively expand it
|
||||
triangleIndices.reserve(triangleIndices.size() + approxNumIndices);
|
||||
}
|
||||
|
||||
auto indexItr = indices.cbegin<const gpu::BufferView::Index>() + partItr->_startIndex;
|
||||
auto indexEnd = indexItr + (partItr->_numIndices - 2);
|
||||
|
||||
// first triangle uses the first three indices
|
||||
triangleIndices.push_back(*(indexItr++) + meshIndexOffset);
|
||||
triangleIndices.push_back(*(indexItr++) + meshIndexOffset);
|
||||
triangleIndices.push_back(*(indexItr++) + meshIndexOffset);
|
||||
|
||||
// the rest use previous and next index
|
||||
uint32_t triangleCount = 1;
|
||||
while (indexItr != indexEnd) {
|
||||
if ((*indexItr) != model::Mesh::PRIMITIVE_RESTART_INDEX) {
|
||||
if (triangleCount % 2 == 0) {
|
||||
// even triangles use first two indices in order
|
||||
triangleIndices.push_back(*(indexItr - 2) + meshIndexOffset);
|
||||
triangleIndices.push_back(*(indexItr - 1) + meshIndexOffset);
|
||||
} else {
|
||||
// odd triangles swap order of first two indices
|
||||
triangleIndices.push_back(*(indexItr - 1) + meshIndexOffset);
|
||||
triangleIndices.push_back(*(indexItr - 2) + meshIndexOffset);
|
||||
}
|
||||
triangleIndices.push_back(*indexItr + meshIndexOffset);
|
||||
++triangleCount;
|
||||
}
|
||||
++indexItr;
|
||||
}
|
||||
}
|
||||
++partItr;
|
||||
}
|
||||
++meshCount;
|
||||
}
|
||||
|
||||
// scale and shift
|
||||
glm::vec3 extentsSize = extents.size();
|
||||
glm::vec3 scaleToFit = dimensions / extentsSize;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
if (extentsSize[i] < 1.0e-6f) {
|
||||
scaleToFit[i] = 1.0f;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < points.size(); ++i) {
|
||||
points[i] = (points[i] * scaleToFit);
|
||||
}
|
||||
|
||||
pointCollection.push_back(points);
|
||||
info.setParams(SHAPE_TYPE_STATIC_MESH, 0.5f * dimensions, _modelURL);
|
||||
} else {
|
||||
ModelEntityItem::computeShapeInfo(info);
|
||||
info.setParams(type, 0.5f * getDimensions());
|
||||
info.setParams(type, 0.5f * dimensions);
|
||||
adjustShapeInfoByRegistration(info);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1198,7 +1198,7 @@ void RenderablePolyVoxEntityItem::computeShapeInfoWorker() {
|
|||
|
||||
QtConcurrent::run([entity, voxelSurfaceStyle, voxelVolumeSize, mesh] {
|
||||
auto polyVoxEntity = std::static_pointer_cast<RenderablePolyVoxEntityItem>(entity);
|
||||
QVector<QVector<glm::vec3>> points;
|
||||
QVector<QVector<glm::vec3>> pointCollection;
|
||||
AABox box;
|
||||
glm::mat4 vtoM = std::static_pointer_cast<RenderablePolyVoxEntityItem>(entity)->voxelToLocalMatrix();
|
||||
|
||||
|
@ -1207,7 +1207,7 @@ void RenderablePolyVoxEntityItem::computeShapeInfoWorker() {
|
|||
// pull each triangle in the mesh into a polyhedron which can be collided with
|
||||
unsigned int i = 0;
|
||||
|
||||
const gpu::BufferView vertexBufferView = mesh->getVertexBuffer();
|
||||
const gpu::BufferView& vertexBufferView = mesh->getVertexBuffer();
|
||||
const gpu::BufferView& indexBufferView = mesh->getIndexBuffer();
|
||||
|
||||
gpu::BufferView::Iterator<const uint32_t> it = indexBufferView.cbegin<uint32_t>();
|
||||
|
@ -1241,9 +1241,9 @@ void RenderablePolyVoxEntityItem::computeShapeInfoWorker() {
|
|||
pointsInPart << p3Model;
|
||||
// add next convex hull
|
||||
QVector<glm::vec3> newMeshPoints;
|
||||
points << newMeshPoints;
|
||||
pointCollection << newMeshPoints;
|
||||
// add points to the new convex hull
|
||||
points[i++] << pointsInPart;
|
||||
pointCollection[i++] << pointsInPart;
|
||||
}
|
||||
} else {
|
||||
unsigned int i = 0;
|
||||
|
@ -1299,19 +1299,19 @@ void RenderablePolyVoxEntityItem::computeShapeInfoWorker() {
|
|||
|
||||
// add next convex hull
|
||||
QVector<glm::vec3> newMeshPoints;
|
||||
points << newMeshPoints;
|
||||
pointCollection << newMeshPoints;
|
||||
// add points to the new convex hull
|
||||
points[i++] << pointsInPart;
|
||||
pointCollection[i++] << pointsInPart;
|
||||
}
|
||||
});
|
||||
}
|
||||
polyVoxEntity->setCollisionPoints(points, box);
|
||||
polyVoxEntity->setCollisionPoints(pointCollection, box);
|
||||
});
|
||||
}
|
||||
|
||||
void RenderablePolyVoxEntityItem::setCollisionPoints(const QVector<QVector<glm::vec3>> points, AABox box) {
|
||||
void RenderablePolyVoxEntityItem::setCollisionPoints(ShapeInfo::PointCollection pointCollection, AABox box) {
|
||||
// this catches the payload from computeShapeInfoWorker
|
||||
if (points.isEmpty()) {
|
||||
if (pointCollection.isEmpty()) {
|
||||
EntityItem::computeShapeInfo(_shapeInfo);
|
||||
return;
|
||||
}
|
||||
|
@ -1325,7 +1325,7 @@ void RenderablePolyVoxEntityItem::setCollisionPoints(const QVector<QVector<glm::
|
|||
QString::number(_registrationPoint.y) + "," +
|
||||
QString::number(_registrationPoint.z);
|
||||
_shapeInfo.setParams(SHAPE_TYPE_COMPOUND, collisionModelDimensions, shapeKey);
|
||||
_shapeInfo.setConvexHulls(points);
|
||||
_shapeInfo.setPointCollection(pointCollection);
|
||||
_meshDirty = false;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -123,7 +123,7 @@ public:
|
|||
std::function<void(int, int, int, uint8_t)> thunk);
|
||||
|
||||
void setMesh(model::MeshPointer mesh);
|
||||
void setCollisionPoints(const QVector<QVector<glm::vec3>> points, AABox box);
|
||||
void setCollisionPoints(ShapeInfo::PointCollection points, AABox box);
|
||||
PolyVox::SimpleVolume<uint8_t>* getVolData() { return _volData; }
|
||||
|
||||
uint8_t getVoxelInternal(int x, int y, int z);
|
||||
|
|
|
@ -1602,14 +1602,20 @@ void EntityItem::updateMass(float mass) {
|
|||
void EntityItem::updateVelocity(const glm::vec3& value) {
|
||||
glm::vec3 velocity = getLocalVelocity();
|
||||
if (velocity != value) {
|
||||
const float MIN_LINEAR_SPEED = 0.001f;
|
||||
if (glm::length(value) < MIN_LINEAR_SPEED) {
|
||||
velocity = ENTITY_ITEM_ZERO_VEC3;
|
||||
if (getShapeType() == SHAPE_TYPE_STATIC_MESH) {
|
||||
if (velocity != Vectors::ZERO) {
|
||||
setLocalVelocity(Vectors::ZERO);
|
||||
}
|
||||
} else {
|
||||
velocity = value;
|
||||
const float MIN_LINEAR_SPEED = 0.001f;
|
||||
if (glm::length(value) < MIN_LINEAR_SPEED) {
|
||||
velocity = ENTITY_ITEM_ZERO_VEC3;
|
||||
} else {
|
||||
velocity = value;
|
||||
}
|
||||
setLocalVelocity(velocity);
|
||||
_dirtyFlags |= Simulation::DIRTY_LINEAR_VELOCITY;
|
||||
}
|
||||
setLocalVelocity(velocity);
|
||||
_dirtyFlags |= Simulation::DIRTY_LINEAR_VELOCITY;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1630,22 +1636,30 @@ void EntityItem::updateDamping(float value) {
|
|||
|
||||
void EntityItem::updateGravity(const glm::vec3& value) {
|
||||
if (_gravity != value) {
|
||||
_gravity = value;
|
||||
_dirtyFlags |= Simulation::DIRTY_LINEAR_VELOCITY;
|
||||
if (getShapeType() == SHAPE_TYPE_STATIC_MESH) {
|
||||
_gravity = Vectors::ZERO;
|
||||
} else {
|
||||
_gravity = value;
|
||||
_dirtyFlags |= Simulation::DIRTY_LINEAR_VELOCITY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EntityItem::updateAngularVelocity(const glm::vec3& value) {
|
||||
glm::vec3 angularVelocity = getLocalAngularVelocity();
|
||||
if (angularVelocity != value) {
|
||||
const float MIN_ANGULAR_SPEED = 0.0002f;
|
||||
if (glm::length(value) < MIN_ANGULAR_SPEED) {
|
||||
angularVelocity = ENTITY_ITEM_ZERO_VEC3;
|
||||
if (getShapeType() == SHAPE_TYPE_STATIC_MESH) {
|
||||
setLocalAngularVelocity(Vectors::ZERO);
|
||||
} else {
|
||||
angularVelocity = value;
|
||||
const float MIN_ANGULAR_SPEED = 0.0002f;
|
||||
if (glm::length(value) < MIN_ANGULAR_SPEED) {
|
||||
angularVelocity = ENTITY_ITEM_ZERO_VEC3;
|
||||
} else {
|
||||
angularVelocity = value;
|
||||
}
|
||||
setLocalAngularVelocity(angularVelocity);
|
||||
_dirtyFlags |= Simulation::DIRTY_ANGULAR_VELOCITY;
|
||||
}
|
||||
setLocalAngularVelocity(angularVelocity);
|
||||
_dirtyFlags |= Simulation::DIRTY_ANGULAR_VELOCITY;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1679,9 +1693,17 @@ void EntityItem::updateCollisionMask(uint8_t value) {
|
|||
}
|
||||
|
||||
void EntityItem::updateDynamic(bool value) {
|
||||
if (_dynamic != value) {
|
||||
_dynamic = value;
|
||||
_dirtyFlags |= Simulation::DIRTY_MOTION_TYPE;
|
||||
if (getDynamic() != value) {
|
||||
// dynamic and STATIC_MESH are incompatible so we check for that case
|
||||
if (value && getShapeType() == SHAPE_TYPE_STATIC_MESH) {
|
||||
if (_dynamic) {
|
||||
_dynamic = false;
|
||||
_dirtyFlags |= Simulation::DIRTY_MOTION_TYPE;
|
||||
}
|
||||
} else {
|
||||
_dynamic = value;
|
||||
_dirtyFlags |= Simulation::DIRTY_MOTION_TYPE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1731,7 +1753,7 @@ void EntityItem::computeCollisionGroupAndFinalMask(int16_t& group, int16_t& mask
|
|||
group = BULLET_COLLISION_GROUP_COLLISIONLESS;
|
||||
mask = 0;
|
||||
} else {
|
||||
if (_dynamic) {
|
||||
if (getDynamic()) {
|
||||
group = BULLET_COLLISION_GROUP_DYNAMIC;
|
||||
} else if (isMovingRelativeToParent() || hasActions()) {
|
||||
group = BULLET_COLLISION_GROUP_KINEMATIC;
|
||||
|
|
|
@ -283,7 +283,7 @@ public:
|
|||
|
||||
void computeCollisionGroupAndFinalMask(int16_t& group, int16_t& mask) const;
|
||||
|
||||
bool getDynamic() const { return _dynamic; }
|
||||
bool getDynamic() const { return SHAPE_TYPE_STATIC_MESH == getShapeType() ? false : _dynamic; }
|
||||
void setDynamic(bool value) { _dynamic = value; }
|
||||
|
||||
virtual bool shouldBePhysical() const { return false; }
|
||||
|
@ -348,7 +348,7 @@ public:
|
|||
void updateDynamic(bool value);
|
||||
void updateLifetime(float value);
|
||||
void updateCreated(uint64_t value);
|
||||
virtual void updateShapeType(ShapeType type) { /* do nothing */ }
|
||||
virtual void setShapeType(ShapeType type) { /* do nothing */ }
|
||||
|
||||
uint32_t getDirtyFlags() const { return _dirtyFlags; }
|
||||
void clearDirtyFlags(uint32_t mask = 0xffffffff) { _dirtyFlags &= ~mask; }
|
||||
|
|
|
@ -88,8 +88,21 @@ void EntityItemProperties::setLastEdited(quint64 usecTime) {
|
|||
_lastEdited = usecTime > _created ? usecTime : _created;
|
||||
}
|
||||
|
||||
const char* shapeTypeNames[] = {"none", "box", "sphere", "plane", "compound", "capsule-x",
|
||||
"capsule-y", "capsule-z", "cylinder-x", "cylinder-y", "cylinder-z"};
|
||||
const char* shapeTypeNames[] = {
|
||||
"none",
|
||||
"box",
|
||||
"sphere",
|
||||
"capsule-x",
|
||||
"capsule-y",
|
||||
"capsule-z",
|
||||
"cylinder-x",
|
||||
"cylinder-y",
|
||||
"cylinder-z",
|
||||
"hull",
|
||||
"plane",
|
||||
"compound",
|
||||
"static-mesh"
|
||||
};
|
||||
|
||||
QHash<QString, ShapeType> stringToShapeTypeLookup;
|
||||
|
||||
|
@ -101,14 +114,16 @@ void buildStringToShapeTypeLookup() {
|
|||
addShapeType(SHAPE_TYPE_NONE);
|
||||
addShapeType(SHAPE_TYPE_BOX);
|
||||
addShapeType(SHAPE_TYPE_SPHERE);
|
||||
addShapeType(SHAPE_TYPE_PLANE);
|
||||
addShapeType(SHAPE_TYPE_COMPOUND);
|
||||
addShapeType(SHAPE_TYPE_CAPSULE_X);
|
||||
addShapeType(SHAPE_TYPE_CAPSULE_Y);
|
||||
addShapeType(SHAPE_TYPE_CAPSULE_Z);
|
||||
addShapeType(SHAPE_TYPE_CYLINDER_X);
|
||||
addShapeType(SHAPE_TYPE_CYLINDER_Y);
|
||||
addShapeType(SHAPE_TYPE_CYLINDER_Z);
|
||||
addShapeType(SHAPE_TYPE_HULL);
|
||||
addShapeType(SHAPE_TYPE_PLANE);
|
||||
addShapeType(SHAPE_TYPE_COMPOUND);
|
||||
addShapeType(SHAPE_TYPE_STATIC_MESH);
|
||||
}
|
||||
|
||||
QString getCollisionGroupAsString(uint8_t group) {
|
||||
|
|
|
@ -77,7 +77,7 @@ bool ModelEntityItem::setProperties(const EntityItemProperties& properties) {
|
|||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(modelURL, setModelURL);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(compoundShapeURL, setCompoundShapeURL);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(textures, setTextures);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, updateShapeType);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, setShapeType);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(jointRotationsSet, setJointRotationsSet);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(jointRotations, setJointRotations);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(jointTranslationsSet, setJointTranslationsSet);
|
||||
|
@ -145,7 +145,7 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
|
|||
dataAt += bytesFromAnimation;
|
||||
}
|
||||
|
||||
READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, updateShapeType);
|
||||
READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, setShapeType);
|
||||
|
||||
if (animationPropertiesChanged) {
|
||||
_dirtyFlags |= Simulation::DIRTY_UPDATEABLE;
|
||||
|
@ -257,37 +257,54 @@ void ModelEntityItem::debugDump() const {
|
|||
qCDebug(entities) << " compound shape URL:" << getCompoundShapeURL();
|
||||
}
|
||||
|
||||
void ModelEntityItem::updateShapeType(ShapeType type) {
|
||||
// BEGIN_TEMPORARY_WORKAROUND
|
||||
// we have allowed inconsistent ShapeType's to be stored in SVO files in the past (this was a bug)
|
||||
// but we are now enforcing the entity properties to be consistent. To make the possible we're
|
||||
// introducing a temporary workaround: we will ignore ShapeType updates that conflict with the
|
||||
// _compoundShapeURL.
|
||||
if (hasCompoundShapeURL()) {
|
||||
type = SHAPE_TYPE_COMPOUND;
|
||||
}
|
||||
// END_TEMPORARY_WORKAROUND
|
||||
|
||||
void ModelEntityItem::setShapeType(ShapeType type) {
|
||||
if (type != _shapeType) {
|
||||
if (type == SHAPE_TYPE_STATIC_MESH && _dynamic) {
|
||||
// dynamic and STATIC_MESH are incompatible
|
||||
// since the shape is being set here we clear the dynamic bit
|
||||
_dynamic = false;
|
||||
_dirtyFlags |= Simulation::DIRTY_MOTION_TYPE;
|
||||
}
|
||||
_shapeType = type;
|
||||
_dirtyFlags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS;
|
||||
}
|
||||
}
|
||||
|
||||
// virtual
|
||||
ShapeType ModelEntityItem::getShapeType() const {
|
||||
if (_shapeType == SHAPE_TYPE_COMPOUND) {
|
||||
return hasCompoundShapeURL() ? SHAPE_TYPE_COMPOUND : SHAPE_TYPE_NONE;
|
||||
} else {
|
||||
return _shapeType;
|
||||
return computeTrueShapeType();
|
||||
}
|
||||
|
||||
ShapeType ModelEntityItem::computeTrueShapeType() const {
|
||||
ShapeType type = _shapeType;
|
||||
if (type == SHAPE_TYPE_STATIC_MESH && _dynamic) {
|
||||
// dynamic is incompatible with STATIC_MESH
|
||||
// shouldn't fall in here but just in case --> fall back to COMPOUND
|
||||
type = SHAPE_TYPE_COMPOUND;
|
||||
}
|
||||
if (type == SHAPE_TYPE_COMPOUND && !hasCompoundShapeURL()) {
|
||||
// no compoundURL set --> fall back to NONE
|
||||
type = SHAPE_TYPE_NONE;
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
void ModelEntityItem::setModelURL(const QString& url) {
|
||||
if (_modelURL != url) {
|
||||
_modelURL = url;
|
||||
_parsedModelURL = QUrl(url);
|
||||
if (_shapeType == SHAPE_TYPE_STATIC_MESH) {
|
||||
_dirtyFlags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ModelEntityItem::setCompoundShapeURL(const QString& url) {
|
||||
if (_compoundShapeURL != url) {
|
||||
ShapeType oldType = computeTrueShapeType();
|
||||
_compoundShapeURL = url;
|
||||
_dirtyFlags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS;
|
||||
_shapeType = _compoundShapeURL.isEmpty() ? SHAPE_TYPE_NONE : SHAPE_TYPE_COMPOUND;
|
||||
if (oldType != computeTrueShapeType()) {
|
||||
_dirtyFlags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -50,9 +50,10 @@ public:
|
|||
virtual bool needsToCallUpdate() const;
|
||||
virtual void debugDump() const;
|
||||
|
||||
void updateShapeType(ShapeType type);
|
||||
void setShapeType(ShapeType type);
|
||||
virtual ShapeType getShapeType() const;
|
||||
|
||||
|
||||
// TODO: Move these to subclasses, or other appropriate abstraction
|
||||
// getters/setters applicable to models and particles
|
||||
|
||||
|
@ -76,7 +77,7 @@ public:
|
|||
}
|
||||
|
||||
// model related properties
|
||||
virtual void setModelURL(const QString& url) { _modelURL = url; _parsedModelURL = QUrl(url); }
|
||||
virtual void setModelURL(const QString& url);
|
||||
virtual void setCompoundShapeURL(const QString& url);
|
||||
|
||||
// Animation related items...
|
||||
|
@ -130,6 +131,7 @@ public:
|
|||
|
||||
private:
|
||||
void setAnimationSettings(const QString& value); // only called for old bitstream format
|
||||
ShapeType computeTrueShapeType() const;
|
||||
|
||||
protected:
|
||||
// these are used:
|
||||
|
|
|
@ -342,7 +342,7 @@ bool ParticleEffectEntityItem::setProperties(const EntityItemProperties& propert
|
|||
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(alpha, setAlpha);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, updateShapeType);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, setShapeType);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(maxParticles, setMaxParticles);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(lifespan, setLifespan);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(isEmitting, setIsEmitting);
|
||||
|
@ -406,7 +406,7 @@ int ParticleEffectEntityItem::readEntitySubclassDataFromBuffer(const unsigned ch
|
|||
READ_ENTITY_PROPERTY(PROP_EMITTING_PARTICLES, bool, setIsEmitting);
|
||||
}
|
||||
|
||||
READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, updateShapeType);
|
||||
READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, setShapeType);
|
||||
READ_ENTITY_PROPERTY(PROP_MAX_PARTICLES, quint32, setMaxParticles);
|
||||
READ_ENTITY_PROPERTY(PROP_LIFESPAN, float, setLifespan);
|
||||
READ_ENTITY_PROPERTY(PROP_EMIT_RATE, float, setEmitRate);
|
||||
|
@ -584,7 +584,7 @@ void ParticleEffectEntityItem::debugDump() const {
|
|||
qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now);
|
||||
}
|
||||
|
||||
void ParticleEffectEntityItem::updateShapeType(ShapeType type) {
|
||||
void ParticleEffectEntityItem::setShapeType(ShapeType type) {
|
||||
if (type != _shapeType) {
|
||||
_shapeType = type;
|
||||
_dirtyFlags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS;
|
||||
|
|
|
@ -95,7 +95,7 @@ public:
|
|||
void setAlphaSpread(float alphaSpread);
|
||||
float getAlphaSpread() const { return _alphaSpread; }
|
||||
|
||||
void updateShapeType(ShapeType type);
|
||||
void setShapeType(ShapeType type);
|
||||
virtual ShapeType getShapeType() const { return _shapeType; }
|
||||
|
||||
virtual void debugDump() const;
|
||||
|
|
|
@ -73,7 +73,7 @@ bool ZoneEntityItem::setProperties(const EntityItemProperties& properties) {
|
|||
|
||||
bool somethingChangedInStage = _stageProperties.setProperties(properties);
|
||||
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, updateShapeType);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, setShapeType);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(compoundShapeURL, setCompoundShapeURL);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(backgroundMode, setBackgroundMode);
|
||||
|
||||
|
@ -117,7 +117,7 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
|
|||
bytesRead += bytesFromStage;
|
||||
dataAt += bytesFromStage;
|
||||
|
||||
READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, updateShapeType);
|
||||
READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, setShapeType);
|
||||
READ_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, QString, setCompoundShapeURL);
|
||||
READ_ENTITY_PROPERTY(PROP_BACKGROUND_MODE, BackgroundMode, setBackgroundMode);
|
||||
|
||||
|
|
|
@ -55,7 +55,7 @@ public:
|
|||
static void setDrawZoneBoundaries(bool value) { _drawZoneBoundaries = value; }
|
||||
|
||||
virtual bool isReadyToComputeShape() { return false; }
|
||||
void updateShapeType(ShapeType type) { _shapeType = type; }
|
||||
void setShapeType(ShapeType type) { _shapeType = type; }
|
||||
virtual ShapeType getShapeType() const;
|
||||
|
||||
virtual bool hasCompoundShapeURL() const { return !_compoundShapeURL.isEmpty(); }
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include <QtGui/QSurfaceFormat>
|
||||
#include <QtOpenGL/QGL>
|
||||
#include <QOpenGLContext>
|
||||
#include <QtCore/QRegularExpression>
|
||||
|
||||
const QSurfaceFormat& getDefaultOpenGLSurfaceFormat() {
|
||||
static QSurfaceFormat format;
|
||||
|
@ -39,6 +40,13 @@ const QGLFormat& getDefaultGLFormat() {
|
|||
return glFormat;
|
||||
}
|
||||
|
||||
int glVersionToInteger(QString glVersion) {
|
||||
QStringList versionParts = glVersion.split(QRegularExpression("[\\.\\s]"));
|
||||
int majorNumber = versionParts[0].toInt();
|
||||
int minorNumber = versionParts[1].toInt();
|
||||
return majorNumber * 100 + minorNumber * 10;
|
||||
}
|
||||
|
||||
QJsonObject getGLContextData() {
|
||||
if (!QOpenGLContext::currentContext()) {
|
||||
return QJsonObject();
|
||||
|
|
|
@ -27,5 +27,6 @@ void setGLFormatVersion(F& format, int major = 4, int minor = 5) { format.setVer
|
|||
const QSurfaceFormat& getDefaultOpenGLSurfaceFormat();
|
||||
const QGLFormat& getDefaultGLFormat();
|
||||
QJsonObject getGLContextData();
|
||||
int glVersionToInteger(QString glVersion);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -396,6 +396,8 @@ void OffscreenQmlSurface::create(QOpenGLContext* shareContext) {
|
|||
|
||||
_renderer->_renderControl->_renderWindow = _proxyWindow;
|
||||
|
||||
connect(_renderer->_quickWindow, &QQuickWindow::focusObjectChanged, this, &OffscreenQmlSurface::onFocusObjectChanged);
|
||||
|
||||
// Create a QML engine.
|
||||
_qmlEngine = new QQmlEngine;
|
||||
if (!_qmlEngine->incubationController()) {
|
||||
|
@ -742,3 +744,21 @@ QVariant OffscreenQmlSurface::returnFromUiThread(std::function<QVariant()> funct
|
|||
|
||||
return function();
|
||||
}
|
||||
|
||||
void OffscreenQmlSurface::onFocusObjectChanged(QObject* object) {
|
||||
if (!object) {
|
||||
setFocusText(false);
|
||||
return;
|
||||
}
|
||||
|
||||
QInputMethodQueryEvent query(Qt::ImEnabled);
|
||||
qApp->sendEvent(object, &query);
|
||||
setFocusText(query.value(Qt::ImEnabled).toBool());
|
||||
}
|
||||
|
||||
void OffscreenQmlSurface::setFocusText(bool newFocusText) {
|
||||
if (newFocusText != _focusText) {
|
||||
_focusText = newFocusText;
|
||||
emit focusTextChanged(_focusText);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ class OffscreenQmlRenderThread;
|
|||
|
||||
class OffscreenQmlSurface : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(bool focusText READ isFocusText NOTIFY focusTextChanged)
|
||||
public:
|
||||
OffscreenQmlSurface();
|
||||
virtual ~OffscreenQmlSurface();
|
||||
|
@ -55,6 +55,7 @@ public:
|
|||
_mouseTranslator = mouseTranslator;
|
||||
}
|
||||
|
||||
bool isFocusText() const { return _focusText; }
|
||||
void pause();
|
||||
void resume();
|
||||
bool isPaused() const;
|
||||
|
@ -70,6 +71,8 @@ public:
|
|||
|
||||
signals:
|
||||
void textureUpdated(unsigned int texture);
|
||||
void focusObjectChanged(QObject* newFocus);
|
||||
void focusTextChanged(bool focusText);
|
||||
|
||||
public slots:
|
||||
void requestUpdate();
|
||||
|
@ -78,6 +81,7 @@ public slots:
|
|||
|
||||
protected:
|
||||
bool filterEnabled(QObject* originalDestination, QEvent* event) const;
|
||||
void setFocusText(bool newFocusText);
|
||||
|
||||
private:
|
||||
QObject* finishQmlLoad(std::function<void(QQmlContext*, QObject*)> f);
|
||||
|
@ -85,6 +89,7 @@ private:
|
|||
|
||||
private slots:
|
||||
void updateQuick();
|
||||
void onFocusObjectChanged(QObject* newFocus);
|
||||
|
||||
private:
|
||||
friend class OffscreenQmlRenderThread;
|
||||
|
@ -97,6 +102,7 @@ private:
|
|||
bool _render{ false };
|
||||
bool _polish{ true };
|
||||
bool _paused{ true };
|
||||
bool _focusText { false };
|
||||
uint8_t _maxFps{ 60 };
|
||||
MouseTranslator _mouseTranslator{ [](const QPointF& p) { return p.toPoint(); } };
|
||||
QWindow* _proxyWindow { nullptr };
|
||||
|
|
|
@ -45,9 +45,11 @@ in vec2 vTexCoord;
|
|||
out vec4 FragColor;
|
||||
|
||||
void main() {
|
||||
|
||||
FragColor = texture(sampler, vTexCoord);
|
||||
FragColor.a *= alpha;
|
||||
if (FragColor.a <= 0.0) {
|
||||
discard;
|
||||
}
|
||||
}
|
||||
|
||||
)FS";
|
||||
|
@ -359,6 +361,94 @@ ShapeWrapperPtr loadSphereSection(ProgramPtr program, float fov, float aspect, i
|
|||
);
|
||||
}
|
||||
|
||||
namespace oglplus {
|
||||
namespace shapes {
|
||||
|
||||
class Laser : public DrawingInstructionWriter, public DrawMode {
|
||||
public:
|
||||
using IndexArray = std::vector<GLuint>;
|
||||
using PosArray = std::vector<float>;
|
||||
/// The type of the index container returned by Indices()
|
||||
// vertex positions
|
||||
PosArray _pos_data;
|
||||
IndexArray _idx_data;
|
||||
unsigned int _prim_count { 0 };
|
||||
|
||||
public:
|
||||
Laser() {
|
||||
int vertices = 2;
|
||||
_pos_data.resize(vertices * 3);
|
||||
_pos_data[0] = 0;
|
||||
_pos_data[1] = 0;
|
||||
_pos_data[2] = 0;
|
||||
|
||||
_pos_data[3] = 0;
|
||||
_pos_data[4] = 0;
|
||||
_pos_data[5] = -1;
|
||||
|
||||
_idx_data.push_back(0);
|
||||
_idx_data.push_back(1);
|
||||
_prim_count = 1;
|
||||
}
|
||||
|
||||
/// Returns the winding direction of faces
|
||||
FaceOrientation FaceWinding(void) const {
|
||||
return FaceOrientation::CCW;
|
||||
}
|
||||
|
||||
/// Queries the bounding sphere coordinates and dimensions
|
||||
template <typename T>
|
||||
void BoundingSphere(Sphere<T>& bounding_sphere) const {
|
||||
bounding_sphere = Sphere<T>(0, 0, -0.5, 0.5);
|
||||
}
|
||||
|
||||
typedef GLuint(Laser::*VertexAttribFunc)(std::vector<GLfloat>&) const;
|
||||
|
||||
/// Makes the vertex positions and returns the number of values per vertex
|
||||
template <typename T>
|
||||
GLuint Positions(std::vector<T>& dest) const {
|
||||
dest.clear();
|
||||
dest.insert(dest.begin(), _pos_data.begin(), _pos_data.end());
|
||||
return 3;
|
||||
}
|
||||
|
||||
typedef VertexAttribsInfo<
|
||||
Laser,
|
||||
std::tuple<VertexPositionsTag>
|
||||
> VertexAttribs;
|
||||
|
||||
|
||||
/// Returns element indices that are used with the drawing instructions
|
||||
const IndexArray & Indices(Default = Default()) const {
|
||||
return _idx_data;
|
||||
}
|
||||
|
||||
/// Returns the instructions for rendering of faces
|
||||
DrawingInstructions Instructions(PrimitiveType primitive) const {
|
||||
DrawingInstructions instr = MakeInstructions();
|
||||
DrawOperation operation;
|
||||
operation.method = DrawOperation::Method::DrawElements;
|
||||
operation.mode = primitive;
|
||||
operation.first = 0;
|
||||
operation.count = _prim_count * 3;
|
||||
operation.restart_index = DrawOperation::NoRestartIndex();
|
||||
operation.phase = 0;
|
||||
AddInstruction(instr, operation);
|
||||
return instr;
|
||||
}
|
||||
|
||||
/// Returns the instructions for rendering of faces
|
||||
DrawingInstructions Instructions(Default = Default()) const {
|
||||
return Instructions(PrimitiveType::Lines);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
ShapeWrapperPtr loadLaser(const ProgramPtr& program) {
|
||||
return std::make_shared<shapes::ShapeWrapper>(shapes::ShapeWrapper("Position", shapes::Laser(), *program));
|
||||
}
|
||||
|
||||
void TextureRecycler::setSize(const uvec2& size) {
|
||||
if (size == _size) {
|
||||
return;
|
||||
|
|
|
@ -64,8 +64,9 @@ ProgramPtr loadCubemapShader();
|
|||
void compileProgram(ProgramPtr & result, const std::string& vs, const std::string& fs);
|
||||
ShapeWrapperPtr loadSkybox(ProgramPtr program);
|
||||
ShapeWrapperPtr loadPlane(ProgramPtr program, float aspect = 1.0f);
|
||||
ShapeWrapperPtr loadSphereSection(ProgramPtr program, float fov = PI / 3.0f * 2.0f, float aspect = 16.0f / 9.0f, int slices = 32, int stacks = 32);
|
||||
|
||||
ShapeWrapperPtr loadSphereSection(ProgramPtr program, float fov = PI / 3.0f * 2.0f, float aspect = 16.0f / 9.0f, int slices = 128, int stacks = 128);
|
||||
ShapeWrapperPtr loadLaser(const ProgramPtr& program);
|
||||
|
||||
|
||||
// A basic wrapper for constructing a framebuffer with a renderbuffer
|
||||
// for the depth attachment and an undefined type for the color attachement
|
||||
|
|
|
@ -44,6 +44,7 @@ Q_DECLARE_METATYPE(QNetworkAccessManager::Operation)
|
|||
Q_DECLARE_METATYPE(JSONCallbackParameters)
|
||||
|
||||
const QString ACCOUNTS_GROUP = "accounts";
|
||||
static const auto METAVERSE_SESSION_ID_HEADER = QString("HFM-SessionID").toLocal8Bit();
|
||||
|
||||
JSONCallbackParameters::JSONCallbackParameters(QObject* jsonCallbackReceiver, const QString& jsonCallbackMethod,
|
||||
QObject* errorCallbackReceiver, const QString& errorCallbackMethod,
|
||||
|
@ -222,8 +223,7 @@ void AccountManager::sendRequest(const QString& path,
|
|||
// if we're allowed to send usage data, include whatever the current session ID is with this request
|
||||
auto& activityLogger = UserActivityLogger::getInstance();
|
||||
if (activityLogger.isEnabled()) {
|
||||
static const QString METAVERSE_SESSION_ID_HEADER = "HFM-SessionID";
|
||||
networkRequest.setRawHeader(METAVERSE_SESSION_ID_HEADER.toLocal8Bit(),
|
||||
networkRequest.setRawHeader(METAVERSE_SESSION_ID_HEADER,
|
||||
uuidStringWithoutCurlyBraces(_sessionID).toLocal8Bit());
|
||||
}
|
||||
|
||||
|
@ -322,6 +322,9 @@ void AccountManager::processReply() {
|
|||
QNetworkReply* requestReply = reinterpret_cast<QNetworkReply*>(sender());
|
||||
|
||||
if (requestReply->error() == QNetworkReply::NoError) {
|
||||
if (requestReply->hasRawHeader(METAVERSE_SESSION_ID_HEADER)) {
|
||||
_sessionID = requestReply->rawHeader(METAVERSE_SESSION_ID_HEADER);
|
||||
}
|
||||
passSuccessToCallback(requestReply);
|
||||
} else {
|
||||
passErrorToCallback(requestReply);
|
||||
|
|
|
@ -26,9 +26,9 @@
|
|||
|
||||
class JSONCallbackParameters {
|
||||
public:
|
||||
JSONCallbackParameters(QObject* jsonCallbackReceiver = NULL, const QString& jsonCallbackMethod = QString(),
|
||||
QObject* errorCallbackReceiver = NULL, const QString& errorCallbackMethod = QString(),
|
||||
QObject* updateReceiver = NULL, const QString& updateSlot = QString());
|
||||
JSONCallbackParameters(QObject* jsonCallbackReceiver = nullptr, const QString& jsonCallbackMethod = QString(),
|
||||
QObject* errorCallbackReceiver = nullptr, const QString& errorCallbackMethod = QString(),
|
||||
QObject* updateReceiver = nullptr, const QString& updateSlot = QString());
|
||||
|
||||
bool isEmpty() const { return !jsonCallbackReceiver && !errorCallbackReceiver; }
|
||||
|
||||
|
@ -86,6 +86,7 @@ public:
|
|||
|
||||
static QJsonObject dataObjectFromResponse(QNetworkReply& requestReply);
|
||||
|
||||
QUuid getSessionID() const { return _sessionID; }
|
||||
void setSessionID(const QUuid& sessionID) { _sessionID = sessionID; }
|
||||
|
||||
public slots:
|
||||
|
@ -139,7 +140,7 @@ private:
|
|||
bool _isWaitingForKeypairResponse { false };
|
||||
QByteArray _pendingPrivateKey;
|
||||
|
||||
QUuid _sessionID;
|
||||
QUuid _sessionID { QUuid::createUuid() };
|
||||
};
|
||||
|
||||
#endif // hifi_AccountManager_h
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include "AddressManager.h"
|
||||
#include "NodeList.h"
|
||||
#include "NetworkLogging.h"
|
||||
#include "UserActivityLogger.h"
|
||||
|
||||
|
||||
const QString ADDRESS_MANAGER_SETTINGS_GROUP = "AddressManager";
|
||||
|
@ -130,6 +131,10 @@ const JSONCallbackParameters& AddressManager::apiCallbackParameters() {
|
|||
}
|
||||
|
||||
bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) {
|
||||
static QString URL_TYPE_USER = "user";
|
||||
static QString URL_TYPE_DOMAIN_ID = "domain_id";
|
||||
static QString URL_TYPE_PLACE = "place";
|
||||
static QString URL_TYPE_NETWORK_ADDRESS = "network_address";
|
||||
if (lookupUrl.scheme() == HIFI_URL_SCHEME) {
|
||||
|
||||
qCDebug(networking) << "Trying to go to URL" << lookupUrl.toString();
|
||||
|
@ -147,6 +152,8 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) {
|
|||
if (handleUsername(lookupUrl.authority())) {
|
||||
// handled a username for lookup
|
||||
|
||||
UserActivityLogger::getInstance().wentTo(trigger, URL_TYPE_USER, lookupUrl.toString());
|
||||
|
||||
// in case we're failing to connect to where we thought this user was
|
||||
// store their username as previous lookup so we can refresh their location via API
|
||||
_previousLookup = lookupUrl;
|
||||
|
@ -157,6 +164,8 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) {
|
|||
if (handleNetworkAddress(lookupUrl.host()
|
||||
+ (lookupUrl.port() == -1 ? "" : ":" + QString::number(lookupUrl.port())), trigger, hostChanged)) {
|
||||
|
||||
UserActivityLogger::getInstance().wentTo(trigger, URL_TYPE_NETWORK_ADDRESS, lookupUrl.toString());
|
||||
|
||||
// a network address lookup clears the previous lookup since we don't expect to re-attempt it
|
||||
_previousLookup.clear();
|
||||
|
||||
|
@ -174,6 +183,8 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) {
|
|||
// we may have a path that defines a relative viewpoint - if so we should jump to that now
|
||||
handlePath(path, trigger);
|
||||
} else if (handleDomainID(lookupUrl.host())){
|
||||
UserActivityLogger::getInstance().wentTo(trigger, URL_TYPE_DOMAIN_ID, lookupUrl.toString());
|
||||
|
||||
// store this domain ID as the previous lookup in case we're failing to connect and want to refresh API info
|
||||
_previousLookup = lookupUrl;
|
||||
|
||||
|
@ -181,6 +192,8 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) {
|
|||
// try to look up the domain ID on the metaverse API
|
||||
attemptDomainIDLookup(lookupUrl.host(), lookupUrl.path(), trigger);
|
||||
} else {
|
||||
UserActivityLogger::getInstance().wentTo(trigger, URL_TYPE_PLACE, lookupUrl.toString());
|
||||
|
||||
// store this place name as the previous lookup in case we fail to connect and want to refresh API info
|
||||
_previousLookup = lookupUrl;
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
|
||||
#include "UserActivityLogger.h"
|
||||
#include <DependencyManager.h>
|
||||
#include "AddressManager.h"
|
||||
|
||||
static const QString USER_ACTIVITY_URL = "/api/v1/user_activities";
|
||||
|
||||
|
@ -125,6 +126,19 @@ void UserActivityLogger::changedDomain(QString domainURL) {
|
|||
}
|
||||
|
||||
void UserActivityLogger::connectedDevice(QString typeOfDevice, QString deviceName) {
|
||||
static QStringList DEVICE_BLACKLIST = {
|
||||
"Desktop",
|
||||
"NullDisplayPlugin",
|
||||
"3D TV - Side by Side Stereo",
|
||||
"3D TV - Interleaved",
|
||||
|
||||
"Keyboard/Mouse"
|
||||
};
|
||||
|
||||
if (DEVICE_BLACKLIST.contains(deviceName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QString ACTION_NAME = "connected_device";
|
||||
QJsonObject actionDetails;
|
||||
const QString TYPE_OF_DEVICE = "type_of_device";
|
||||
|
@ -148,12 +162,34 @@ void UserActivityLogger::loadedScript(QString scriptName) {
|
|||
|
||||
}
|
||||
|
||||
void UserActivityLogger::wentTo(QString destinationType, QString destinationName) {
|
||||
void UserActivityLogger::wentTo(AddressManager::LookupTrigger lookupTrigger, QString destinationType, QString destinationName) {
|
||||
// Only accept these types of triggers. Other triggers are usually used internally in AddressManager.
|
||||
QString trigger;
|
||||
switch (lookupTrigger) {
|
||||
case AddressManager::UserInput:
|
||||
trigger = "UserInput";
|
||||
break;
|
||||
case AddressManager::Back:
|
||||
trigger = "Back";
|
||||
break;
|
||||
case AddressManager::Forward:
|
||||
trigger = "Forward";
|
||||
break;
|
||||
case AddressManager::StartupFromSettings:
|
||||
trigger = "StartupFromSettings";
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const QString ACTION_NAME = "went_to";
|
||||
QJsonObject actionDetails;
|
||||
const QString TRIGGER_TYPE_KEY = "trigger";
|
||||
const QString DESTINATION_TYPE_KEY = "destination_type";
|
||||
const QString DESTINATION_NAME_KEY = "detination_name";
|
||||
|
||||
actionDetails.insert(TRIGGER_TYPE_KEY, trigger);
|
||||
actionDetails.insert(DESTINATION_TYPE_KEY, destinationType);
|
||||
actionDetails.insert(DESTINATION_NAME_KEY, destinationName);
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include <QNetworkReply>
|
||||
|
||||
#include <SettingHandle.h>
|
||||
#include "AddressManager.h"
|
||||
|
||||
class UserActivityLogger : public QObject {
|
||||
Q_OBJECT
|
||||
|
@ -42,7 +43,7 @@ public slots:
|
|||
void changedDomain(QString domainURL);
|
||||
void connectedDevice(QString typeOfDevice, QString deviceName);
|
||||
void loadedScript(QString scriptName);
|
||||
void wentTo(QString destinationType, QString destinationName);
|
||||
void wentTo(AddressManager::LookupTrigger trigger, QString destinationType, QString destinationName);
|
||||
|
||||
private slots:
|
||||
void requestError(QNetworkReply& errorReply);
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
//
|
||||
// UserActivityLoggerScriptingInterface.h
|
||||
// libraries/networking/src
|
||||
//
|
||||
// Created by Ryan Huffman on 6/06/16.
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "UserActivityLoggerScriptingInterface.h"
|
||||
#include "UserActivityLogger.h"
|
||||
|
||||
void UserActivityLoggerScriptingInterface::enabledEdit() {
|
||||
logAction("enabled_edit");
|
||||
}
|
||||
|
||||
void UserActivityLoggerScriptingInterface::openedMarketplace() {
|
||||
logAction("opened_marketplace");
|
||||
}
|
||||
|
||||
void UserActivityLoggerScriptingInterface::toggledAway(bool isAway) {
|
||||
logAction("toggled_away", { { "is_away", isAway } });
|
||||
}
|
||||
|
||||
void UserActivityLoggerScriptingInterface::logAction(QString action, QJsonObject details) {
|
||||
QMetaObject::invokeMethod(&UserActivityLogger::getInstance(), "logAction",
|
||||
Q_ARG(QString, action),
|
||||
Q_ARG(QJsonObject, details));
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
//
|
||||
// UserActivityLoggerScriptingInterface.h
|
||||
// libraries/networking/src
|
||||
//
|
||||
// Created by Ryan Huffman on 6/06/16.
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_UserActivityLoggerScriptingInterface_h
|
||||
#define hifi_UserActivityLoggerScriptingInterface_h
|
||||
|
||||
#include <QObject>
|
||||
#include <QJsonObject>
|
||||
|
||||
#include <DependencyManager.h>
|
||||
|
||||
class UserActivityLoggerScriptingInterface : public QObject, public Dependency {
|
||||
Q_OBJECT
|
||||
public:
|
||||
Q_INVOKABLE void enabledEdit();
|
||||
Q_INVOKABLE void openedMarketplace();
|
||||
Q_INVOKABLE void toggledAway(bool isAway);
|
||||
|
||||
private:
|
||||
void logAction(QString action, QJsonObject details = {});
|
||||
};
|
||||
|
||||
#endif // hifi_UserActivityLoggerScriptingInterface_h
|
|
@ -49,7 +49,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
|
|||
case PacketType::EntityAdd:
|
||||
case PacketType::EntityEdit:
|
||||
case PacketType::EntityData:
|
||||
return VERSION_ENTITIES_PROPERLY_ENCODE_SHAPE_EDITS;
|
||||
return VERSION_MODEL_ENTITIES_SUPPORT_STATIC_MESH;
|
||||
case PacketType::AvatarIdentity:
|
||||
case PacketType::AvatarData:
|
||||
case PacketType::BulkAvatarData:
|
||||
|
|
|
@ -180,6 +180,7 @@ const PacketVersion VERSION_LIGHT_HAS_FALLOFF_RADIUS = 57;
|
|||
const PacketVersion VERSION_ENTITIES_NO_FLY_ZONES = 58;
|
||||
const PacketVersion VERSION_ENTITIES_MORE_SHAPES = 59;
|
||||
const PacketVersion VERSION_ENTITIES_PROPERLY_ENCODE_SHAPE_EDITS = 60;
|
||||
const PacketVersion VERSION_MODEL_ENTITIES_SUPPORT_STATIC_MESH = 61;
|
||||
|
||||
enum class AvatarMixerPacketVersion : PacketVersion {
|
||||
TranslationSupport = 17,
|
||||
|
|
|
@ -159,6 +159,11 @@ PhysicsMotionType EntityMotionState::computePhysicsMotionType() const {
|
|||
}
|
||||
assert(entityTreeIsLocked());
|
||||
|
||||
if (_entity->getShapeType() == SHAPE_TYPE_STATIC_MESH
|
||||
|| (_body && _body->getCollisionShape()->getShapeType() == TRIANGLE_MESH_SHAPE_PROXYTYPE)) {
|
||||
return MOTION_TYPE_STATIC;
|
||||
}
|
||||
|
||||
if (_entity->getDynamic()) {
|
||||
if (!_entity->getParentID().isNull()) {
|
||||
// if something would have been dynamic but is a child of something else, force it to be kinematic, instead.
|
||||
|
|
|
@ -203,35 +203,37 @@ void ObjectMotionState::handleEasyChanges(uint32_t& flags) {
|
|||
}
|
||||
}
|
||||
|
||||
if (flags & Simulation::DIRTY_LINEAR_VELOCITY) {
|
||||
btVector3 newLinearVelocity = glmToBullet(getObjectLinearVelocity());
|
||||
if (!(flags & Simulation::DIRTY_PHYSICS_ACTIVATION)) {
|
||||
float delta = (newLinearVelocity - _body->getLinearVelocity()).length();
|
||||
if (delta > ACTIVATION_LINEAR_VELOCITY_DELTA) {
|
||||
flags |= Simulation::DIRTY_PHYSICS_ACTIVATION;
|
||||
if (_body->getCollisionShape()->getShapeType() != TRIANGLE_MESH_SHAPE_PROXYTYPE) {
|
||||
if (flags & Simulation::DIRTY_LINEAR_VELOCITY) {
|
||||
btVector3 newLinearVelocity = glmToBullet(getObjectLinearVelocity());
|
||||
if (!(flags & Simulation::DIRTY_PHYSICS_ACTIVATION)) {
|
||||
float delta = (newLinearVelocity - _body->getLinearVelocity()).length();
|
||||
if (delta > ACTIVATION_LINEAR_VELOCITY_DELTA) {
|
||||
flags |= Simulation::DIRTY_PHYSICS_ACTIVATION;
|
||||
}
|
||||
}
|
||||
}
|
||||
_body->setLinearVelocity(newLinearVelocity);
|
||||
_body->setLinearVelocity(newLinearVelocity);
|
||||
|
||||
btVector3 newGravity = glmToBullet(getObjectGravity());
|
||||
if (!(flags & Simulation::DIRTY_PHYSICS_ACTIVATION)) {
|
||||
float delta = (newGravity - _body->getGravity()).length();
|
||||
if (delta > ACTIVATION_GRAVITY_DELTA ||
|
||||
(delta > 0.0f && _body->getGravity().length2() == 0.0f)) {
|
||||
flags |= Simulation::DIRTY_PHYSICS_ACTIVATION;
|
||||
btVector3 newGravity = glmToBullet(getObjectGravity());
|
||||
if (!(flags & Simulation::DIRTY_PHYSICS_ACTIVATION)) {
|
||||
float delta = (newGravity - _body->getGravity()).length();
|
||||
if (delta > ACTIVATION_GRAVITY_DELTA ||
|
||||
(delta > 0.0f && _body->getGravity().length2() == 0.0f)) {
|
||||
flags |= Simulation::DIRTY_PHYSICS_ACTIVATION;
|
||||
}
|
||||
}
|
||||
_body->setGravity(newGravity);
|
||||
}
|
||||
_body->setGravity(newGravity);
|
||||
}
|
||||
if (flags & Simulation::DIRTY_ANGULAR_VELOCITY) {
|
||||
btVector3 newAngularVelocity = glmToBullet(getObjectAngularVelocity());
|
||||
if (!(flags & Simulation::DIRTY_PHYSICS_ACTIVATION)) {
|
||||
float delta = (newAngularVelocity - _body->getAngularVelocity()).length();
|
||||
if (delta > ACTIVATION_ANGULAR_VELOCITY_DELTA) {
|
||||
flags |= Simulation::DIRTY_PHYSICS_ACTIVATION;
|
||||
if (flags & Simulation::DIRTY_ANGULAR_VELOCITY) {
|
||||
btVector3 newAngularVelocity = glmToBullet(getObjectAngularVelocity());
|
||||
if (!(flags & Simulation::DIRTY_PHYSICS_ACTIVATION)) {
|
||||
float delta = (newAngularVelocity - _body->getAngularVelocity()).length();
|
||||
if (delta > ACTIVATION_ANGULAR_VELOCITY_DELTA) {
|
||||
flags |= Simulation::DIRTY_PHYSICS_ACTIVATION;
|
||||
}
|
||||
}
|
||||
_body->setAngularVelocity(newAngularVelocity);
|
||||
}
|
||||
_body->setAngularVelocity(newAngularVelocity);
|
||||
}
|
||||
|
||||
if (flags & Simulation::DIRTY_MATERIAL) {
|
||||
|
|
|
@ -217,7 +217,7 @@ void PhysicalEntitySimulation::getObjectsToAddToPhysics(VectorOfMotionStates& re
|
|||
} else if (entity->isReadyToComputeShape()) {
|
||||
ShapeInfo shapeInfo;
|
||||
entity->computeShapeInfo(shapeInfo);
|
||||
int numPoints = shapeInfo.getMaxNumPoints();
|
||||
int numPoints = shapeInfo.getLargestSubshapePointCount();
|
||||
if (numPoints > MAX_HULL_POINTS) {
|
||||
qWarning() << "convex hull with" << numPoints
|
||||
<< "points for entity" << entity->getName()
|
||||
|
@ -231,7 +231,7 @@ void PhysicalEntitySimulation::getObjectsToAddToPhysics(VectorOfMotionStates& re
|
|||
result.push_back(motionState);
|
||||
entityItr = _entitiesToAddToPhysics.erase(entityItr);
|
||||
} else {
|
||||
//qDebug() << "Warning! Failed to generate new shape for entity." << entity->getName();
|
||||
//qWarning() << "Failed to generate new shape for entity." << entity->getName();
|
||||
++entityItr;
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -67,7 +67,8 @@ static const btVector3 _unitSphereDirections[NUM_UNIT_SPHERE_DIRECTIONS] = {
|
|||
};
|
||||
|
||||
|
||||
btConvexHullShape* ShapeFactory::createConvexHull(const QVector<glm::vec3>& points) {
|
||||
// util method
|
||||
btConvexHullShape* createConvexHull(const ShapeInfo::PointList& points) {
|
||||
assert(points.size() > 0);
|
||||
|
||||
btConvexHullShape* hull = new btConvexHullShape();
|
||||
|
@ -158,6 +159,84 @@ btConvexHullShape* ShapeFactory::createConvexHull(const QVector<glm::vec3>& poin
|
|||
return hull;
|
||||
}
|
||||
|
||||
// util method
|
||||
btTriangleIndexVertexArray* createStaticMeshArray(const ShapeInfo& info) {
|
||||
assert(info.getType() == SHAPE_TYPE_STATIC_MESH); // should only get here for mesh shapes
|
||||
|
||||
const ShapeInfo::PointCollection& pointCollection = info.getPointCollection();
|
||||
assert(pointCollection.size() == 1); // should only have one mesh
|
||||
|
||||
const ShapeInfo::PointList& pointList = pointCollection[0];
|
||||
assert(pointList.size() > 2); // should have at least one triangle's worth of points
|
||||
|
||||
const ShapeInfo::TriangleIndices& triangleIndices = info.getTriangleIndices();
|
||||
assert(triangleIndices.size() > 2); // should have at least one triangle's worth of indices
|
||||
|
||||
// allocate mesh buffers
|
||||
btIndexedMesh mesh;
|
||||
int32_t numIndices = triangleIndices.size();
|
||||
const int32_t VERTICES_PER_TRIANGLE = 3;
|
||||
mesh.m_numTriangles = numIndices / VERTICES_PER_TRIANGLE;
|
||||
if (numIndices < INT16_MAX) {
|
||||
// small number of points so we can use 16-bit indices
|
||||
mesh.m_triangleIndexBase = new unsigned char[sizeof(int16_t) * (size_t)numIndices];
|
||||
mesh.m_indexType = PHY_SHORT;
|
||||
mesh.m_triangleIndexStride = VERTICES_PER_TRIANGLE * sizeof(int16_t);
|
||||
} else {
|
||||
mesh.m_triangleIndexBase = new unsigned char[sizeof(int32_t) * (size_t)numIndices];
|
||||
mesh.m_indexType = PHY_INTEGER;
|
||||
mesh.m_triangleIndexStride = VERTICES_PER_TRIANGLE * sizeof(int32_t);
|
||||
}
|
||||
mesh.m_numVertices = pointList.size();
|
||||
mesh.m_vertexBase = new unsigned char[VERTICES_PER_TRIANGLE * sizeof(btScalar) * (size_t)mesh.m_numVertices];
|
||||
mesh.m_vertexStride = VERTICES_PER_TRIANGLE * sizeof(btScalar);
|
||||
mesh.m_vertexType = PHY_FLOAT;
|
||||
|
||||
// copy data into buffers
|
||||
btScalar* vertexData = static_cast<btScalar*>((void*)(mesh.m_vertexBase));
|
||||
for (int32_t i = 0; i < mesh.m_numVertices; ++i) {
|
||||
int32_t j = i * VERTICES_PER_TRIANGLE;
|
||||
const glm::vec3& point = pointList[i];
|
||||
vertexData[j] = point.x;
|
||||
vertexData[j + 1] = point.y;
|
||||
vertexData[j + 2] = point.z;
|
||||
}
|
||||
if (numIndices < INT16_MAX) {
|
||||
int16_t* indices = static_cast<int16_t*>((void*)(mesh.m_triangleIndexBase));
|
||||
for (int32_t i = 0; i < numIndices; ++i) {
|
||||
indices[i] = triangleIndices[i];
|
||||
}
|
||||
} else {
|
||||
int32_t* indices = static_cast<int32_t*>((void*)(mesh.m_triangleIndexBase));
|
||||
for (int32_t i = 0; i < numIndices; ++i) {
|
||||
indices[i] = triangleIndices[i];
|
||||
}
|
||||
}
|
||||
|
||||
// store buffers in a new dataArray and return the pointer
|
||||
// (external StaticMeshShape will own all of the data that was allocated here)
|
||||
btTriangleIndexVertexArray* dataArray = new btTriangleIndexVertexArray;
|
||||
dataArray->addIndexedMesh(mesh, mesh.m_indexType);
|
||||
return dataArray;
|
||||
}
|
||||
|
||||
// util method
|
||||
void deleteStaticMeshArray(btTriangleIndexVertexArray* dataArray) {
|
||||
assert(dataArray);
|
||||
IndexedMeshArray& meshes = dataArray->getIndexedMeshArray();
|
||||
for (int32_t i = 0; i < meshes.size(); ++i) {
|
||||
btIndexedMesh mesh = meshes[i];
|
||||
mesh.m_numTriangles = 0;
|
||||
delete [] mesh.m_triangleIndexBase;
|
||||
mesh.m_triangleIndexBase = nullptr;
|
||||
mesh.m_numVertices = 0;
|
||||
delete [] mesh.m_vertexBase;
|
||||
mesh.m_vertexBase = nullptr;
|
||||
}
|
||||
meshes.clear();
|
||||
delete dataArray;
|
||||
}
|
||||
|
||||
btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) {
|
||||
btCollisionShape* shape = NULL;
|
||||
int type = info.getType();
|
||||
|
@ -179,15 +258,15 @@ btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) {
|
|||
}
|
||||
break;
|
||||
case SHAPE_TYPE_COMPOUND: {
|
||||
const QVector<QVector<glm::vec3>>& points = info.getPoints();
|
||||
const ShapeInfo::PointCollection& pointCollection = info.getPointCollection();
|
||||
uint32_t numSubShapes = info.getNumSubShapes();
|
||||
if (numSubShapes == 1) {
|
||||
shape = createConvexHull(info.getPoints()[0]);
|
||||
shape = createConvexHull(pointCollection[0]);
|
||||
} else {
|
||||
auto compound = new btCompoundShape();
|
||||
btTransform trans;
|
||||
trans.setIdentity();
|
||||
foreach (QVector<glm::vec3> hullPoints, points) {
|
||||
foreach (const ShapeInfo::PointList& hullPoints, pointCollection) {
|
||||
btConvexHullShape* hull = createConvexHull(hullPoints);
|
||||
compound->addChildShape (trans, hull);
|
||||
}
|
||||
|
@ -195,6 +274,11 @@ btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) {
|
|||
}
|
||||
}
|
||||
break;
|
||||
case SHAPE_TYPE_STATIC_MESH: {
|
||||
btTriangleIndexVertexArray* dataArray = createStaticMeshArray(info);
|
||||
shape = new StaticMeshShape(dataArray);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (shape) {
|
||||
if (glm::length2(info.getOffset()) > MIN_SHAPE_OFFSET * MIN_SHAPE_OFFSET) {
|
||||
|
@ -228,3 +312,14 @@ void ShapeFactory::deleteShape(btCollisionShape* shape) {
|
|||
}
|
||||
delete shape;
|
||||
}
|
||||
|
||||
// the dataArray must be created before we create the StaticMeshShape
|
||||
ShapeFactory::StaticMeshShape::StaticMeshShape(btTriangleIndexVertexArray* dataArray)
|
||||
: btBvhTriangleMeshShape(dataArray, true), _dataArray(dataArray) {
|
||||
assert(dataArray);
|
||||
}
|
||||
|
||||
ShapeFactory::StaticMeshShape::~StaticMeshShape() {
|
||||
deleteStaticMeshArray(_dataArray);
|
||||
_dataArray = nullptr;
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue