Merge branch 'master' of https://github.com/highfidelity/hifi into audio/throttle-filter
|
@ -266,6 +266,9 @@ void Agent::handleSelectedAudioFormat(QSharedPointer<ReceivedMessage> message) {
|
|||
}
|
||||
|
||||
void Agent::selectAudioFormat(const QString& selectedCodecName) {
|
||||
if (_selectedCodecName == selectedCodecName) {
|
||||
return;
|
||||
}
|
||||
_selectedCodecName = selectedCodecName;
|
||||
|
||||
qDebug() << "Selected Codec:" << _selectedCodecName;
|
||||
|
@ -351,9 +354,15 @@ void Agent::executeScript() {
|
|||
Transform audioTransform;
|
||||
audioTransform.setTranslation(scriptedAvatar->getPosition());
|
||||
audioTransform.setRotation(scriptedAvatar->getOrientation());
|
||||
AbstractAudioInterface::emitAudioPacket(audio.data(), audio.size(), audioSequenceNumber,
|
||||
QByteArray encodedBuffer;
|
||||
if (_encoder) {
|
||||
_encoder->encode(audio, encodedBuffer);
|
||||
} else {
|
||||
encodedBuffer = audio;
|
||||
}
|
||||
AbstractAudioInterface::emitAudioPacket(encodedBuffer.data(), encodedBuffer.size(), audioSequenceNumber,
|
||||
audioTransform, scriptedAvatar->getPosition(), glm::vec3(0),
|
||||
PacketType::MicrophoneAudioNoEcho);
|
||||
PacketType::MicrophoneAudioNoEcho, _selectedCodecName);
|
||||
});
|
||||
|
||||
auto avatarHashMap = DependencyManager::set<AvatarHashMap>();
|
||||
|
|
|
@ -294,7 +294,9 @@ void EntityServer::readAdditionalConfiguration(const QJsonObject& settingsSectio
|
|||
}
|
||||
|
||||
if (readOptionString("entityEditFilter", settingsSectionObject, _entityEditFilter) && !_entityEditFilter.isEmpty()) {
|
||||
// Fetch script from file synchronously. We don't want the server processing edits while a restarting entity server is fetching from a DOS'd source.
|
||||
// Tell the tree that we have a filter, so that it doesn't accept edits until we have a filter function set up.
|
||||
std::static_pointer_cast<EntityTree>(_tree)->setHasEntityFilter(true);
|
||||
// Now fetch script from file asynchronously.
|
||||
QUrl scriptURL(_entityEditFilter);
|
||||
|
||||
// The following should be abstracted out for use in Agent.cpp (and maybe later AvatarMixer.cpp)
|
||||
|
@ -314,7 +316,7 @@ void EntityServer::readAdditionalConfiguration(const QJsonObject& settingsSectio
|
|||
// FIXME: handle atp rquests setup here. See Agent::requestScript()
|
||||
qInfo() << "Requesting script at URL" << qPrintable(scriptRequest->getUrl().toString());
|
||||
scriptRequest->send();
|
||||
_scriptRequestLoop.exec(); // Block here, but allow the request to be processed and its signals to be handled.
|
||||
qDebug() << "script request sent";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -351,6 +353,7 @@ static bool hadUncaughtExceptions(QScriptEngine& engine, const QString& fileName
|
|||
return false;
|
||||
}
|
||||
void EntityServer::scriptRequestFinished() {
|
||||
qDebug() << "script request completed";
|
||||
auto scriptRequest = qobject_cast<ResourceRequest*>(sender());
|
||||
const QString urlString = scriptRequest->getUrl().toString();
|
||||
if (scriptRequest && scriptRequest->getResult() == ResourceRequest::Success) {
|
||||
|
@ -364,9 +367,7 @@ void EntityServer::scriptRequestFinished() {
|
|||
return hadUncaughtExceptions(_entityEditFilterEngine, _entityEditFilter);
|
||||
});
|
||||
scriptRequest->deleteLater();
|
||||
if (_scriptRequestLoop.isRunning()) {
|
||||
_scriptRequestLoop.quit();
|
||||
}
|
||||
qDebug() << "script request filter processed";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -379,10 +380,8 @@ void EntityServer::scriptRequestFinished() {
|
|||
}
|
||||
// Hard stop of the assignment client on failure. We don't want anyone to think they have a filter in place when they don't.
|
||||
// Alas, only indications will be the above logging with assignment client restarting repeatedly, and clients will not see any entities.
|
||||
qDebug() << "script request failure causing stop";
|
||||
stop();
|
||||
if (_scriptRequestLoop.isRunning()) {
|
||||
_scriptRequestLoop.quit();
|
||||
}
|
||||
}
|
||||
|
||||
void EntityServer::nodeAdded(SharedNodePointer node) {
|
||||
|
|
|
@ -80,7 +80,6 @@ private:
|
|||
|
||||
QString _entityEditFilter{};
|
||||
QScriptEngine _entityEditFilterEngine{};
|
||||
QEventLoop _scriptRequestLoop{};
|
||||
};
|
||||
|
||||
#endif // hifi_EntityServer_h
|
||||
|
|
4
cmake/externals/wasapi/CMakeLists.txt
vendored
|
@ -6,8 +6,8 @@ if (WIN32)
|
|||
include(ExternalProject)
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL http://hifi-public.s3.amazonaws.com/dependencies/qtaudio_wasapi6.zip
|
||||
URL_MD5 fcac808c1ba0b0f5b44ea06e2612ebab
|
||||
URL http://hifi-public.s3.amazonaws.com/dependencies/qtaudio_wasapi7.zip
|
||||
URL_MD5 bc2861e50852dd590cdc773a14a041a7
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
|
|
|
@ -372,6 +372,13 @@
|
|||
"help": "Password used for basic HTTP authentication. Leave this blank if you do not want to change it.",
|
||||
"value-hidden": true
|
||||
},
|
||||
{
|
||||
"name": "verify_http_password",
|
||||
"label": "Verify HTTP Password",
|
||||
"type": "password",
|
||||
"help": "Must match the password entered above for change to be saved.",
|
||||
"value-hidden": true
|
||||
},
|
||||
{
|
||||
"name": "maximum_user_capacity",
|
||||
"label": "Maximum User Capacity",
|
||||
|
|
|
@ -904,10 +904,18 @@ function saveSettings() {
|
|||
var formJSON = form2js('settings-form', ".", false, cleanupFormValues, true);
|
||||
|
||||
// check if we've set the basic http password - if so convert it to base64
|
||||
var canPost = true;
|
||||
if (formJSON["security"]) {
|
||||
var password = formJSON["security"]["http_password"];
|
||||
var verify_password = formJSON["security"]["verify_http_password"];
|
||||
if (password && password.length > 0) {
|
||||
formJSON["security"]["http_password"] = sha256_digest(password);
|
||||
if (password != verify_password) {
|
||||
bootbox.alert({"message": "Passwords must match!", "title":"Password Error"});
|
||||
canPost = false;
|
||||
} else {
|
||||
formJSON["security"]["http_password"] = sha256_digest(password);
|
||||
delete formJSON["security"]["verify_http_password"];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -923,7 +931,9 @@ function saveSettings() {
|
|||
$(this).blur();
|
||||
|
||||
// POST the form JSON to the domain-server settings.json endpoint so the settings are saved
|
||||
postSettings(formJSON);
|
||||
if (canPost) {
|
||||
postSettings(formJSON);
|
||||
}
|
||||
}
|
||||
|
||||
$('body').on('click', '.save-button', function(e){
|
||||
|
|
|
@ -1,50 +1,12 @@
|
|||
{
|
||||
"name": "Standard to Action",
|
||||
"when": "Application.NavigationFocused",
|
||||
"name": "Standard to UI Navigation Action",
|
||||
"channels": [
|
||||
{ "from": "Standard.DU", "to": "Actions.UiNavVertical" },
|
||||
{ "from": "Standard.DD", "to": "Actions.UiNavVertical", "filters": "invert" },
|
||||
{ "from": "Standard.DL", "to": "Actions.UiNavLateral", "filters": "invert" },
|
||||
{ "from": "Standard.DR", "to": "Actions.UiNavLateral" },
|
||||
{ "from": "Standard.LB", "to": "Actions.UiNavGroup","filters": "invert" },
|
||||
{ "from": "Standard.RB", "to": "Actions.UiNavGroup" },
|
||||
{ "from": [ "Standard.A", "Standard.X" ], "to": "Actions.UiNavSelect" },
|
||||
{ "from": [ "Standard.B", "Standard.Y" ], "to": "Actions.UiNavBack" },
|
||||
{ "from": [ "Standard.RTClick", "Standard.LTClick" ], "to": "Actions.UiNavSelect" },
|
||||
{
|
||||
"from": "Standard.LX", "to": "Actions.UiNavLateral",
|
||||
"filters": [
|
||||
{ "type": "deadZone", "min": 0.95 },
|
||||
"constrainToInteger",
|
||||
{ "type": "pulse", "interval": 0.4 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"from": "Standard.LY", "to": "Actions.UiNavVertical",
|
||||
"filters": [
|
||||
"invert",
|
||||
{ "type": "deadZone", "min": 0.95 },
|
||||
"constrainToInteger",
|
||||
{ "type": "pulse", "interval": 0.4 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"from": "Standard.RX", "to": "Actions.UiNavLateral",
|
||||
"filters": [
|
||||
{ "type": "deadZone", "min": 0.95 },
|
||||
"constrainToInteger",
|
||||
{ "type": "pulse", "interval": 0.4 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"from": "Standard.RY", "to": "Actions.UiNavVertical",
|
||||
"filters": [
|
||||
"invert",
|
||||
{ "type": "deadZone", "min": 0.95 },
|
||||
"constrainToInteger",
|
||||
{ "type": "pulse", "interval": 0.4 }
|
||||
]
|
||||
}
|
||||
{ "from": "Standard.RB", "to": "Actions.UiNavGroup" }
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
@ -56,6 +56,9 @@
|
|||
{ "from": "GamePad.A", "to": "Standard.A" },
|
||||
{ "from": "GamePad.B", "to": "Standard.B" },
|
||||
{ "from": "GamePad.X", "to": "Standard.X" },
|
||||
{ "from": "GamePad.Y", "to": "Standard.Y" }
|
||||
{ "from": "GamePad.Y", "to": "Standard.Y" },
|
||||
|
||||
{ "from": [ "Standard.A", "Standard.X" ], "to": "Actions.UiNavSelect" },
|
||||
{ "from": [ "Standard.B", "Standard.Y" ], "to": "Actions.UiNavBack" }
|
||||
]
|
||||
}
|
||||
|
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 124 KiB |
63
interface/resources/icons/circle.svg
Normal file
|
@ -0,0 +1,63 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="70mm"
|
||||
height="70mm"
|
||||
viewBox="0 0 248.0315 248.03149"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="circle.svg">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="1.979899"
|
||||
inkscape:cx="79.162615"
|
||||
inkscape:cy="188.36943"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1377"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-804.33071)">
|
||||
<circle
|
||||
style="fill:#4b504f;fill-opacity:1;stroke:#feffff;stroke-width:0.22076035;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="path4140"
|
||||
cx="124.46446"
|
||||
cy="928.42853"
|
||||
r="121.41819" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
68
interface/resources/icons/edit-icon.svg
Normal file
|
@ -0,0 +1,68 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 50 49.999998"
|
||||
xml:space="preserve"
|
||||
id="svg4239"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="edit-icon.svg"
|
||||
width="50"
|
||||
height="50"><metadata
|
||||
id="metadata4336"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs4334" /><sodipodi:namedview
|
||||
pagecolor="#615f71"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1663"
|
||||
inkscape:window-height="1023"
|
||||
id="namedview4332"
|
||||
showgrid="false"
|
||||
inkscape:zoom="4.717641"
|
||||
inkscape:cx="27.234437"
|
||||
inkscape:cy="40.361616"
|
||||
inkscape:window-x="412"
|
||||
inkscape:window-y="188"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg4239" /><style
|
||||
type="text/css"
|
||||
id="style4241">
|
||||
.st0{fill:#414042;}
|
||||
.st1{fill:#FFFFFF;}
|
||||
.st2{fill:#1E1E1E;}
|
||||
.st3{fill:#333333;}
|
||||
</style><g
|
||||
id="Layer_3"
|
||||
transform="matrix(2.0055009,0,0,2.0055009,-26.481547,-324.23978)" /><path
|
||||
style="fill:#ffffff;fill-opacity:1"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path4268"
|
||||
d="m 17.615466,29.206401 c -2.807702,2.807702 -5.615403,5.615403 -8.4231037,8.423104 -0.601651,-0.60165 -1.403851,-1.40385 -2.005501,-2.005501 2.807701,-2.807701 5.6154017,-5.615402 8.4231037,-8.423104 l -2.406601,-2.206051 c -3.2088017,3.208802 -6.4176027,6.417603 -9.8269547,9.626405 -0.60165,0.60165 -1.00275,1.40385 -1.2033,2.406601 -0.401101,2.607151 -0.802201,5.214302 -1.20330104,8.022004 0.20055004,0 0.40110004,0 0.60165004,0 2.607151,-0.601651 5.013753,-1.002751 7.620904,-1.604401 0.4011,-0.20055 1.0027497,-0.4011 1.4038497,-0.802201 3.409352,-3.409351 6.818703,-6.818703 10.228055,-10.228054 l -3.208801,-3.208802 z"
|
||||
class="st3" /><path
|
||||
style="fill:#ffffff;fill-opacity:1"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path4270"
|
||||
d="m 30.851772,7.3464388 c 1.00275,-1.00275 2.206051,-2.206051 3.409351,-3.409351 0.4011,-0.4011 1.002751,-0.4011 1.403851,0 2.005501,2.005501 4.011002,4.011002 6.016502,6.016503 0.401101,0.4011002 0.401101,1.0027502 0,1.6044002 -1.2033,1.203303 -2.406601,2.406603 -3.409351,3.409354 -2.406601,-2.406603 -4.813202,-5.0137542 -7.420353,-7.6209062 z"
|
||||
class="st3" /><path
|
||||
style="fill:#ffffff;fill-opacity:1"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path4272"
|
||||
d="m 33.659473,25.9976 c -0.60165,0.20055 -0.802201,0 -1.203301,-0.4011 -0.8022,-0.802201 -1.6044,-1.604401 -2.406601,-2.406601 2.005501,-2.005501 3.810452,-3.810452 5.815953,-5.815953 0.20055,-0.20055 0.4011,-0.4011 0.4011,-0.4011 -2.607151,-2.607151 -5.013752,-5.013754 -7.620903,-7.8214562 -0.200551,0.20055 -0.200551,0.4011 -0.401101,0.601651 -2.005501,2.0055002 -3.810451,3.8104532 -5.815952,5.8159542 -3.609902,-3.609903 -7.019253,-7.0192552 -10.629155,-10.6291572 -0.20055,-0.20055 -0.60165,-0.60165 -0.8022,-0.8022 -2.0055007,-1.403851 -4.4121017,-1.203301 -6.0165027,0.60165 -1.604401,1.804951 -1.403851,4.412102 0.20055,6.0165032 5.2143017,5.214304 10.6291547,10.629157 15.8434567,15.843459 1.804951,1.804951 3.409352,3.409352 5.214302,5.214303 0.200551,0.20055 0.401101,0.4011 0.200551,0.8022 -0.200551,0.8022 -0.401101,1.604401 -0.401101,2.406601 -0.4011,5.013752 2.807702,9.425854 7.821454,10.829705 2.005501,0.60165 4.011002,0.4011 6.217053,-0.4011 -0.20055,-0.20055 -0.4011,-0.4011 -0.601651,-0.4011 -1.40385,-1.403851 -3.008251,-3.008252 -4.412102,-4.412102 -1.00275,-1.002751 -1.2033,-2.807702 -0.20055,-3.810452 0.802201,-0.802201 1.604401,-1.604401 2.406601,-2.406601 1.203301,-1.002751 2.607152,-1.002751 3.609902,0 0.20055,0.20055 0.4011,0.4011 0.60165,0.60165 1.403851,1.403851 3.008252,3.008251 4.412102,4.412102 0,0 0.20055,0 0.20055,0 0.200551,-0.8022 0.401101,-1.604401 0.401101,-2.607151 C 47.697979,29.607502 40.678726,23.992099 33.659473,25.9976 Z M 8.1896113,9.1513898 c -0.8022,0 -1.40385,-0.60165 -1.40385,-1.40385 0,-0.802201 0.60165,-1.604401 1.40385,-1.604401 0.802201,0 1.604401,0.8022 1.604401,1.403851 0,0.8022 -0.8022,1.6044 -1.604401,1.6044 z M 29.046821,13.764044 c 0.60165,0.601651 1.2033,1.403851 2.005501,2.005501 -1.403851,1.403851 -2.807702,2.807702 -4.211552,4.211552 -0.601651,-0.60165 -1.403851,-1.40385 -2.005501,-2.005501 1.40385,-1.40385 2.807701,-2.807701 4.211552,-4.211552 z"
|
||||
class="st3" /></svg>
|
After Width: | Height: | Size: 5 KiB |
46
interface/resources/icons/tablet-icons/bubble-i.svg
Normal file
|
@ -0,0 +1,46 @@
|
|||
<?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" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g id="Layer_2">
|
||||
</g>
|
||||
<g id="Layer_1">
|
||||
<g>
|
||||
<path class="st0" d="M23.2,24.1c-0.8,0.9-1.5,1.8-2.2,2.6c-0.1,0.2-0.1,0.5-0.1,0.7c0.1,1.7,0.2,3.4,0.2,5.1
|
||||
c0,0.8-0.4,1.2-1.1,1.3c-0.7,0.1-1.3-0.4-1.4-1.1c-0.2-2.2-0.3-4.3-0.5-6.5c0-0.3,0.1-0.7,0.4-1c1.1-1.5,2.3-3,3.4-4.5
|
||||
c0.6-0.7,1.6-1.6,2.6-1.6c0.3,0,1.1,0,1.4,0c0.8-0.1,1.3,0.1,1.9,0.9c1,1.2,1.5,2.3,2.4,3.6c0.7,1.1,1.4,1.6,2.9,1.9
|
||||
c1.1,0.2,2.2,0.5,3.3,0.8c0.3,0.1,0.6,0.2,0.8,0.3c0.5,0.3,0.7,0.8,0.6,1.3c-0.1,0.5-0.5,0.7-1,0.8c-0.4,0-0.9,0-1.3-0.1
|
||||
c-1.4-0.3-2.7-0.6-4.1-0.9c-0.8-0.2-1.5-0.6-2.1-1.1c-0.3-0.3-0.6-0.5-0.9-0.8c0,0.3,0,0.5,0,0.7c0,1.2,0,2.4,0,3.6
|
||||
c0,0.4-0.3,12.6-0.1,16.8c0,0.5-0.1,1-0.2,1.5c-0.2,0.7-0.6,1-1.4,1.1c-0.8,0-1.4-0.3-1.7-1c-0.2-0.5-0.3-1.1-0.4-1.6
|
||||
c-0.4-4.6-0.9-12.9-1.1-13.8c-0.1-0.8-0.2-1.1-0.3-2.1c-0.1-0.5-0.1-0.9-0.1-1.3C23.3,27.9,23.2,26.1,23.2,24.1z"/>
|
||||
<path class="st0" d="M28.2,14.6c0,1.4-1.1,2.6-2.6,2.6l0,0c-1.4,0-2.6-1.1-2.6-2.6v-1.6c0-1.4,1.1-2.6,2.6-2.6l0,0
|
||||
c1.4,0,2.6,1.1,2.6,2.6V14.6z"/>
|
||||
</g>
|
||||
<path class="st0" d="M8.4,38.9c2.8,3.2,6.4,5.5,10.5,6.7c0.6,0.2,1.3,0.1,1.7-0.3c0.4-0.3,0.6-0.6,0.7-1c0.2-0.5,0.1-1.1-0.2-1.5
|
||||
c-0.3-0.5-0.7-0.8-1.2-1c-1.6-0.5-3.2-1.2-4.6-2.1c-1.5-0.9-2.8-2.1-4-3.4c-0.4-0.4-0.9-0.7-1.5-0.7c-0.5,0-1,0.2-1.3,0.5
|
||||
c-0.4,0.4-0.6,0.8-0.7,1.4C7.8,38,8,38.5,8.4,38.9z"/>
|
||||
<path class="st0" d="M43.7,36.8c0.3-0.4,0.4-1,0.3-1.5c-0.1-0.5-0.4-1-0.8-1.3c-0.3-0.2-0.7-0.3-1.1-0.3c-0.7,0-1.3,0.3-1.7,0.9
|
||||
c-1.1,1.6-2.4,3-4,4.2c-1.2,0.9-2.5,1.7-3.9,2.3c-0.5,0.2-0.9,0.6-1.1,1.1c-0.2,0.5-0.2,1,0,1.5c0.4,1,1.6,1.5,2.6,1
|
||||
c1.7-0.7,3.3-1.7,4.8-2.8C40.7,40.4,42.4,38.7,43.7,36.8z"/>
|
||||
<path class="st0" d="M5.1,33.2c0.5,0.4,1.2,0.4,1.8,0.2c0.5-0.2,0.9-0.6,1.1-1.1c0.2-0.5,0.2-1,0-1.5c-0.1-0.4-0.4-0.7-0.7-0.9
|
||||
c-0.3-0.2-0.7-0.3-1.1-0.3c-0.2,0-0.5,0-0.7,0.1c-1,0.4-1.5,1.6-1.1,2.6C4.5,32.7,4.7,33,5.1,33.2z"/>
|
||||
<path class="st0" d="M45.4,27.3c-0.2,0-0.3-0.1-0.5-0.1c-0.9,0-1.7,0.6-1.9,1.5c-0.1,0.5-0.1,1.1,0.2,1.5c0.3,0.5,0.7,0.8,1.2,0.9
|
||||
c0.2,0,0.3,0.1,0.5,0.1c0.9,0,1.7-0.6,1.9-1.5c0.1-0.5,0.1-1.1-0.2-1.5C46.4,27.8,45.9,27.5,45.4,27.3z"/>
|
||||
<path class="st0" d="M8.6,12c-0.3-0.2-0.7-0.3-1-0.3c-0.3,0-0.7,0.1-1,0.3c-0.3,0.2-0.6,0.4-0.7,0.7c-2,3.5-3.1,7.4-3.1,11.4
|
||||
c0,0.2,0,0.4,0,0.6c0,0.5,0.2,1,0.6,1.4c0.4,0.4,0.9,0.6,1.4,0.6v0.4l0.1-0.4c0.5,0,1-0.2,1.4-0.6c0.4-0.4,0.6-0.9,0.5-1.4
|
||||
c0-0.2,0-0.4,0-0.5c0-3.3,0.9-6.6,2.6-9.4C9.9,13.8,9.6,12.6,8.6,12z"/>
|
||||
<path class="st0" d="M39.3,11.4c-0.1,0.5,0.1,1.1,0.4,1.5c1.1,1.4,2,3,2.6,4.6c0.6,1.6,1,3.2,1.2,4.9c0,0.5,0.3,1,0.6,1.3
|
||||
c0.4,0.4,1,0.6,1.5,0.5c0.5,0,1-0.3,1.4-0.7c0.3-0.4,0.5-0.9,0.5-1.5c-0.4-4.2-2-8.2-4.6-11.6c-0.4-0.5-1-0.8-1.6-0.8
|
||||
c-0.4,0-0.9,0.1-1.2,0.4C39.7,10.4,39.4,10.8,39.3,11.4z"/>
|
||||
<path class="st0" d="M12.2,6.3c-0.5,0-0.9,0.2-1.3,0.5c-0.4,0.3-0.7,0.8-0.7,1.4c-0.1,0.5,0.1,1.1,0.4,1.5c0.7,0.8,2,1,2.8,0.3
|
||||
c0.4-0.3,0.7-0.8,0.7-1.3c0.1-0.5-0.1-1.1-0.4-1.5C13.4,6.6,12.8,6.3,12.2,6.3z"/>
|
||||
<path class="st0" d="M37.2,5.2c-0.3-0.2-0.7-0.3-1.1-0.3c-0.7,0-1.3,0.3-1.7,0.9c-0.3,0.4-0.4,1-0.3,1.5c0.1,0.5,0.4,1,0.9,1.3
|
||||
C36,9.2,37.3,8.9,37.9,8C38.4,7.1,38.2,5.8,37.2,5.2z"/>
|
||||
<path class="st0" d="M16.5,4c-0.2,0.5-0.3,1-0.1,1.5c0.4,1,1.5,1.6,2.6,1.2c3.3-1.2,6.8-1.4,10.2-0.6c0.6,0.1,1.2,0,1.7-0.4
|
||||
c0.4-0.3,0.6-0.7,0.7-1.1c0.1-0.5,0-1.1-0.3-1.5c-0.3-0.5-0.7-0.8-1.3-0.9c-1.6-0.4-3.3-0.5-4.9-0.5c-2.6,0-5.1,0.4-7.5,1.3
|
||||
C17.1,3.2,16.7,3.6,16.5,4z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.7 KiB |
25
interface/resources/icons/tablet-icons/edit-i.svg
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g id="Layer_2">
|
||||
</g>
|
||||
<g>
|
||||
<path class="st0" d="M20.7,29.7c-2.2,2.2-4.4,4.4-6.7,6.7c-0.5-0.5-1.1-1.1-1.6-1.6c2.2-2.2,4.4-4.4,6.7-6.7l-1.8-1.8
|
||||
c-2.6,2.5-5.1,5.1-7.7,7.6c-0.5,0.5-0.9,1.1-1,1.8C8.3,37.8,8,39.8,7.7,42c0.2,0,0.4,0,0.5,0c2-0.4,4-0.8,5.9-1.2
|
||||
c0.4-0.1,0.8-0.3,1.1-0.6c2.7-2.6,5.3-5.3,8-8L20.7,29.7z"/>
|
||||
<path class="st0" d="M31.1,11c0.8-0.8,1.8-1.8,2.7-2.7C34.2,8,34.6,8,34.9,8.4c1.6,1.6,3.1,3.1,4.7,4.7c0.4,0.4,0.4,0.8,0,1.2
|
||||
c-0.9,0.9-1.8,1.8-2.7,2.7C35,15,33.1,13,31.1,11z"/>
|
||||
<path class="st0" d="M33,25.9c-0.4,0.1-0.6,0-0.9-0.2c-0.6-0.6-1.3-1.3-1.9-1.9c1.5-1.5,3.1-3.1,4.6-4.6c0.1-0.1,0.3-0.3,0.3-0.3
|
||||
c-2-2-3.9-4-5.9-6.1c-0.1,0.2-0.2,0.3-0.4,0.5c-1.5,1.5-3,3-4.6,4.6c-2.8-2.8-5.6-5.6-8.4-8.4c-0.2-0.2-0.4-0.4-0.6-0.6
|
||||
c-1.5-1.2-3.5-1-4.8,0.4c-1.2,1.4-1.1,3.5,0.2,4.8c4.2,4.2,8.3,8.3,12.5,12.5c1.4,1.4,2.7,2.7,4.1,4.1c0.2,0.2,0.2,0.4,0.2,0.7
|
||||
c-0.2,0.6-0.3,1.2-0.3,1.9c-0.3,4,2.3,7.5,6.1,8.5c1.6,0.4,3.2,0.3,4.8-0.3c-0.1-0.2-0.3-0.2-0.4-0.4c-1.2-1.2-2.3-2.3-3.5-3.5
|
||||
c-0.8-0.9-0.9-2.1-0.1-3c0.6-0.7,1.3-1.3,2-2c0.9-0.8,2-0.8,2.9,0c0.2,0.2,0.3,0.3,0.5,0.5c1.2,1.2,2.3,2.3,3.5,3.5
|
||||
c0.1,0,0.1,0,0.2,0c0.1-0.7,0.3-1.3,0.3-2C43.9,28.7,38.5,24.3,33,25.9z M12.9,12.6c-0.6,0-1.2-0.5-1.2-1.2s0.5-1.2,1.2-1.2
|
||||
c0.6,0,1.2,0.6,1.2,1.2C14.1,12,13.6,12.6,12.9,12.6z M29.3,16.3c0.5,0.5,1,1.1,1.6,1.6c-1.1,1.1-2.2,2.2-3.3,3.3
|
||||
c-0.5-0.5-1.1-1.1-1.6-1.6C27.1,18.5,28.2,17.4,29.3,16.3z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
16
interface/resources/icons/tablet-icons/goto-i.svg
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?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" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g id="Layer_2">
|
||||
</g>
|
||||
<g id="Layer_1">
|
||||
<path class="st0" d="M47.2,41.3l-9.1-9.1c-0.8-0.8-1.9-1.1-3-1l-2.4-2.4c1.8-2.6,2.8-5.7,2.8-9c0-8.9-7.2-16.1-16.1-16.1
|
||||
S3.3,11,3.3,19.8c0,8.9,7.2,16.1,16.1,16.1c4.1,0,7.8-1.5,10.6-4l2.2,2.2c-0.2,1.1,0.1,2.2,1,3l9.1,9.1c1.4,1.4,3.6,1.4,4.9,0
|
||||
C48.5,44.9,48.5,42.7,47.2,41.3z M19.4,32.2c-6.8,0-12.3-5.5-12.3-12.3c0-6.8,5.5-12.3,12.3-12.3s12.3,5.5,12.3,12.3
|
||||
C31.8,26.6,26.2,32.2,19.4,32.2z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 832 B |
27
interface/resources/icons/tablet-icons/help-i.svg
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?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" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
.st1{fill:#FFFFFF;stroke:#FFFFFF;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
</style>
|
||||
<g id="Layer_2">
|
||||
</g>
|
||||
<g id="Layer_1">
|
||||
<path class="st0" d="M11.3,36.7c-0.2-0.3-0.4-0.6-0.6-0.8C6.3,29,5.8,21.8,10,14.8c4.3-7.2,11-10.4,19.3-9.3
|
||||
c7.9,1.1,13.4,5.8,15.8,13.4c2.4,7.8,0.4,14.6-5.5,20.1c-3.8,3.6-8.4,5.1-13.5,5.1c-7.3,0-14.6-0.1-21.9-0.1c-0.2,0-0.5,0-1,0
|
||||
C6,41.5,8.6,39.1,11.3,36.7z M10.9,41c0.3,0,0.6,0.1,0.8,0.1c4.9,0,9.9,0,14.8,0C35.6,41,42.8,34,43,25.1
|
||||
c0.2-9.3-7.3-16.9-16.6-16.7c-6.9,0.1-12,3.5-14.9,9.7c-2.9,6.4-1.8,12.5,2.8,17.8c0.3,0.4,0.7,0.7,1,1.1
|
||||
C13.9,38.3,12.5,39.6,10.9,41z"/>
|
||||
<path class="st1" d="M22.5,20.9c0-0.7,0.1-1.4,0.3-2c0.2-0.6,0.6-1.1,1-1.6c0.4-0.4,0.9-0.8,1.6-1c0.6-0.2,1.3-0.4,2-0.4
|
||||
c0.6,0,1.2,0.1,1.7,0.3c0.5,0.2,1,0.4,1.4,0.8c0.4,0.3,0.7,0.8,1,1.3c0.2,0.5,0.3,1.1,0.3,1.8c0,0.5-0.1,0.9-0.2,1.2
|
||||
c-0.1,0.3-0.2,0.6-0.4,0.9c-0.2,0.3-0.4,0.5-0.6,0.7c-0.2,0.2-0.4,0.4-0.7,0.6c-0.3,0.2-0.5,0.4-0.7,0.6c-0.2,0.2-0.4,0.4-0.6,0.7
|
||||
c-0.2,0.3-0.3,0.5-0.4,0.9c-0.1,0.3-0.1,0.8-0.1,1.2H26c0-0.6,0-1.1,0.1-1.5c0.1-0.4,0.2-0.8,0.3-1.1c0.1-0.3,0.3-0.6,0.5-0.8
|
||||
c0.2-0.2,0.4-0.5,0.7-0.7c0.2-0.2,0.4-0.4,0.6-0.5c0.2-0.2,0.4-0.3,0.5-0.5c0.2-0.2,0.3-0.4,0.4-0.7c0.1-0.2,0.1-0.5,0.1-0.9
|
||||
c0-0.4-0.1-0.8-0.2-1.1c-0.1-0.3-0.3-0.5-0.5-0.7c-0.2-0.2-0.4-0.3-0.7-0.4c-0.2-0.1-0.4-0.1-0.6-0.1c-0.8,0-1.5,0.3-1.9,0.8
|
||||
c-0.4,0.6-0.6,1.3-0.6,2.2H22.5z"/>
|
||||
<path class="st0" d="M27.6,29.8c-0.1,0-0.3-0.1-0.4-0.1c-0.8,0-1.4,0.5-1.6,1.2c-0.1,0.4,0,0.9,0.2,1.3c0.2,0.4,0.6,0.7,1,0.8
|
||||
c0.1,0,0.3,0.1,0.4,0.1c0.8,0,1.4-0.5,1.6-1.2c0.1-0.4,0.1-0.9-0.2-1.3C28.4,30.1,28,29.9,27.6,29.8z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2 KiB |
28
interface/resources/icons/tablet-icons/ignore-i.svg
Normal file
|
@ -0,0 +1,28 @@
|
|||
<?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" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g id="Layer_2">
|
||||
</g>
|
||||
<g id="Layer_1">
|
||||
<path class="st0" d="M27.1,42.3c-1.4,0.3-2.7,0.5-4.1,0.5c-9.3,0-16.8-7.5-16.8-16.8S13.8,9.2,23,9.2S39.8,16.7,39.8,26
|
||||
c0,0.2,0,0.3,0,0.5c0.7-0.7,1.4-1,2.4-1.5C41.7,14.9,33.3,6.8,23,6.8C12.4,6.8,3.8,15.4,3.8,26S12.4,45.2,23,45.2
|
||||
c1.7,0,3.4-0.3,5.1-0.7C27.8,43.6,27.5,43,27.1,42.3z"/>
|
||||
<path class="st0" d="M32.6,20.7L32.6,20.7c-0.4-0.4-0.7-0.4-1.2-0.3c0,0-5.9,0.7-8.4,0.7h-0.1c-2.5,0-8.6-0.9-8.6-0.9
|
||||
c-0.4-0.1-0.9,0-1.2,0.4L13,21c-0.1,0.3-0.1,0.6-0.1,1c0.1,0.3,0.3,0.6,0.6,0.7c1,0.4,4.9,1.8,5.9,2.1c0.1,0,0.6,0.1,0.6,0.9
|
||||
c0,0.9-0.3,4.6-0.7,6.4c-0.4,1.8-1.2,4-1.2,4c-0.1,0.6,0.1,1.3,0.7,1.5l0.7,0.3c0.3,0.1,0.6,0.1,0.9,0c0.3-0.1,0.4-0.4,0.6-0.7
|
||||
l2.1-6.4l1.9,6.5c0.1,0.3,0.3,0.6,0.6,0.7c0.1,0.1,0.3,0.1,0.4,0.1c0.1,0,0.3,0,0.4-0.1l0.7-0.3c0.6-0.1,0.9-0.7,0.7-1.3
|
||||
c0,0-0.6-2.4-1-4.3c-0.3-1.2-0.4-3-0.4-4.3c0-0.9-0.1-1.6-0.1-2.2c0-0.3,0.1-0.6,0.6-0.7c0.1,0,5.5-1.9,5.5-1.9
|
||||
c0.4-0.1,0.6-0.4,0.7-0.9C32.9,21.5,32.9,21,32.6,20.7z"/>
|
||||
<circle class="st0" cx="23" cy="17" r="2.5"/>
|
||||
<g>
|
||||
<path class="st0" d="M43.1,43L43.1,43L43.1,43z"/>
|
||||
<path class="st0" d="M41,35.6l4.6-4.6c0.7-0.7,0.7-1.9,0-2.6c-0.7-0.7-1.9-0.7-2.6,0L38.5,33l-4.6-4.6c-0.7-0.7-1.9-0.7-2.6,0
|
||||
s-0.7,1.9,0,2.6l4.6,4.6l-4.6,4.6c-0.7,0.7-0.7,1.9,0,2.6s1.9,0.7,2.6,0l4.6-4.6l4.6,4.6c0.7,0.7,1.9,0.7,2.6,0
|
||||
c0.7-0.7,0.7-1.9,0-2.6L41,35.6z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
177
interface/resources/icons/tablet-icons/ignore.svg
Normal file
|
@ -0,0 +1,177 @@
|
|||
<?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 200.1" style="enable-background:new 0 0 50 200.1;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#414042;}
|
||||
.st1{fill:#FFFFFF;}
|
||||
.st2{fill:#1E1E1E;}
|
||||
.st3{fill:#333333;}
|
||||
</style>
|
||||
<g id="Layer_2">
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M50.1,146.1c0,2.2-1.8,4-4,4h-42c-2.2,0-4-1.8-4-4v-42c0-2.2,1.8-4,4-4h42c2.2,0,4,1.8,4,4V146.1z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M50,196.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,4V196.1z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st1" d="M50,46c0,2.2-1.8,4-4,4H4c-2.2,0-4-1.8-4-4V4c0-2.2,1.8-4,4-4h42c2.2,0,4,1.8,4,4V46z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st2" d="M50,96.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,4V96.1z"/>
|
||||
</g>
|
||||
</g>
|
||||
<path class="st1" d="M24.7,81.2c-6.2,0-11.2-5-11.2-11.2c0-6.2,5-11.2,11.2-11.2c6.2,0,11.2,5,11.2,11.2
|
||||
C35.9,76.2,30.9,81.2,24.7,81.2z M24.7,60.3c-5.4,0-9.8,4.4-9.8,9.8c0,5.4,4.4,9.8,9.8,9.8c5.4,0,9.8-4.4,9.8-9.8
|
||||
C34.5,64.6,30.1,60.3,24.7,60.3z"/>
|
||||
<g>
|
||||
<path class="st3" d="M7.7,42.5v-6.4h1.2v6.4H7.7z"/>
|
||||
<path class="st3" d="M14.7,41.8c-0.5,0.5-1.1,0.8-1.7,0.8c-0.4,0-0.8-0.1-1.2-0.3c-0.4-0.2-0.7-0.4-0.9-0.7c-0.3-0.3-0.5-0.7-0.6-1
|
||||
s-0.2-0.8-0.2-1.2c0-0.4,0.1-0.9,0.2-1.2c0.2-0.4,0.4-0.7,0.6-1c0.3-0.3,0.6-0.5,1-0.7c0.4-0.2,0.8-0.3,1.2-0.3
|
||||
c0.6,0,1.1,0.1,1.5,0.4s0.7,0.6,0.9,1l-0.9,0.7c-0.2-0.3-0.4-0.6-0.6-0.7s-0.6-0.3-0.9-0.3c-0.3,0-0.5,0.1-0.7,0.2
|
||||
c-0.2,0.1-0.4,0.3-0.5,0.5c-0.2,0.2-0.3,0.4-0.4,0.7c-0.1,0.3-0.1,0.5-0.1,0.8c0,0.3,0,0.6,0.1,0.8c0.1,0.3,0.2,0.5,0.4,0.7
|
||||
c0.2,0.2,0.4,0.3,0.6,0.5c0.2,0.1,0.5,0.2,0.7,0.2c0.6,0,1.1-0.3,1.6-0.9v-0.5h-1.3v-0.9h2.3v3.3h-1V41.8z"/>
|
||||
<path class="st3" d="M18.3,38.4v4.1h-1.2v-6.4h1l3.3,4.2v-4.2h1.2v6.4h-1L18.3,38.4z"/>
|
||||
<path class="st3" d="M26.8,42.5c-0.5,0-0.9-0.1-1.2-0.3s-0.7-0.4-1-0.7c-0.3-0.3-0.5-0.6-0.6-1c-0.1-0.4-0.2-0.8-0.2-1.2
|
||||
c0-0.4,0.1-0.8,0.2-1.2s0.4-0.7,0.6-1c0.3-0.3,0.6-0.5,1-0.7s0.8-0.3,1.2-0.3c0.5,0,0.9,0.1,1.2,0.3s0.7,0.4,1,0.7
|
||||
c0.3,0.3,0.5,0.7,0.6,1c0.1,0.4,0.2,0.8,0.2,1.2c0,0.4-0.1,0.8-0.2,1.2c-0.2,0.4-0.4,0.7-0.6,1c-0.3,0.3-0.6,0.5-1,0.7
|
||||
C27.7,42.4,27.2,42.5,26.8,42.5z M25,39.3c0,0.3,0,0.5,0.1,0.8c0.1,0.3,0.2,0.5,0.4,0.7c0.2,0.2,0.3,0.4,0.6,0.5s0.5,0.2,0.8,0.2
|
||||
c0.3,0,0.5-0.1,0.8-0.2s0.4-0.3,0.6-0.5c0.1-0.2,0.3-0.4,0.3-0.7s0.1-0.5,0.1-0.8c0-0.3,0-0.5-0.1-0.8c-0.1-0.3-0.2-0.5-0.4-0.7
|
||||
c-0.2-0.2-0.3-0.4-0.6-0.5c-0.2-0.1-0.5-0.2-0.7-0.2c-0.3,0-0.5,0.1-0.8,0.2s-0.4,0.3-0.6,0.5c-0.2,0.2-0.3,0.4-0.3,0.7
|
||||
C25.1,38.8,25,39,25,39.3z"/>
|
||||
<path class="st3" d="M31,42.5v-6.4h2.8c0.3,0,0.6,0.1,0.8,0.2s0.5,0.3,0.6,0.5c0.2,0.2,0.3,0.4,0.4,0.7c0.1,0.3,0.2,0.5,0.2,0.8
|
||||
c0,0.4-0.1,0.8-0.3,1.1c-0.2,0.3-0.5,0.6-0.8,0.7l1.5,2.4h-1.4l-1.3-2.1h-1.2v2.1H31z M32.3,39.3h1.6c0.1,0,0.2,0,0.3-0.1
|
||||
c0.1-0.1,0.2-0.1,0.3-0.2c0.1-0.1,0.1-0.2,0.2-0.3s0.1-0.3,0.1-0.4c0-0.1,0-0.3-0.1-0.4s-0.1-0.2-0.2-0.3c-0.1-0.1-0.2-0.2-0.3-0.2
|
||||
c-0.1-0.1-0.2-0.1-0.3-0.1h-1.5V39.3z"/>
|
||||
<path class="st3" d="M41.8,41.4v1.1h-4.4v-6.4h4.4v1.1h-3.1v1.5h2.7v1h-2.7v1.7H41.8z"/>
|
||||
</g>
|
||||
<path class="st1" d="M27.1,79.5c-0.8,0.2-1.6,0.3-2.4,0.3c-5.4,0-9.8-4.4-9.8-9.8c0-5.4,4.4-9.8,9.8-9.8c5.4,0,9.8,4.4,9.8,9.8
|
||||
c0,0.1,0,0.2,0,0.3c0.4-0.4,0.8-0.6,1.4-0.9c-0.3-5.9-5.2-10.6-11.2-10.6c-6.2,0-11.2,5-11.2,11.2c0,6.2,5,11.2,11.2,11.2
|
||||
c1,0,2-0.2,3-0.4C27.5,80.4,27.3,80,27.1,79.5z"/>
|
||||
<path class="st1" d="M31.2,66.5L31.2,66.5c-0.3-0.3-0.5-0.3-0.8-0.2c0,0-4,0.5-5.7,0.5c0,0-0.1,0-0.1,0c-1.7,0-5.8-0.6-5.8-0.6
|
||||
c-0.3-0.1-0.6,0-0.8,0.3l-0.1,0.2c-0.1,0.2-0.1,0.4-0.1,0.7c0.1,0.2,0.2,0.4,0.4,0.5c0.7,0.3,3.3,1.2,4,1.4c0.1,0,0.4,0.1,0.4,0.6
|
||||
c0,0.6-0.2,3.1-0.5,4.3c-0.3,1.2-0.8,2.7-0.8,2.7c-0.1,0.4,0.1,0.9,0.5,1l0.5,0.2c0.2,0.1,0.4,0.1,0.6,0c0.2-0.1,0.3-0.3,0.4-0.5
|
||||
l1.4-4.3l1.3,4.4c0.1,0.2,0.2,0.4,0.4,0.5c0.1,0.1,0.2,0.1,0.3,0.1c0.1,0,0.2,0,0.3-0.1l0.5-0.2c0.4-0.1,0.6-0.5,0.5-0.9
|
||||
c0,0-0.4-1.6-0.7-2.9c-0.2-0.8-0.3-2-0.3-2.9c0-0.6-0.1-1.1-0.1-1.5c0-0.2,0.1-0.4,0.4-0.5c0.1,0,3.7-1.3,3.7-1.3
|
||||
c0.3-0.1,0.4-0.3,0.5-0.6C31.4,66.9,31.4,66.7,31.2,66.5z"/>
|
||||
<circle class="st1" cx="24.7" cy="64" r="1.7"/>
|
||||
<g>
|
||||
<path class="st1" d="M7.7,92.3V86h1.2v6.4H7.7z"/>
|
||||
<path class="st1" d="M14.7,91.6c-0.5,0.5-1.1,0.8-1.7,0.8c-0.4,0-0.8-0.1-1.2-0.3c-0.4-0.2-0.7-0.4-0.9-0.7c-0.3-0.3-0.5-0.7-0.6-1
|
||||
s-0.2-0.8-0.2-1.2c0-0.4,0.1-0.9,0.2-1.2c0.2-0.4,0.4-0.7,0.6-1c0.3-0.3,0.6-0.5,1-0.7c0.4-0.2,0.8-0.3,1.2-0.3
|
||||
c0.6,0,1.1,0.1,1.5,0.4s0.7,0.6,0.9,1L14.6,88c-0.2-0.3-0.4-0.6-0.6-0.7S13.4,87,13.1,87c-0.3,0-0.5,0.1-0.7,0.2
|
||||
c-0.2,0.1-0.4,0.3-0.5,0.5c-0.2,0.2-0.3,0.4-0.4,0.7c-0.1,0.3-0.1,0.5-0.1,0.8c0,0.3,0,0.6,0.1,0.8c0.1,0.3,0.2,0.5,0.4,0.7
|
||||
c0.2,0.2,0.4,0.3,0.6,0.5c0.2,0.1,0.5,0.2,0.7,0.2c0.6,0,1.1-0.3,1.6-0.9V90h-1.3v-0.9h2.3v3.3h-1V91.6z"/>
|
||||
<path class="st1" d="M18.3,88.2v4.1h-1.2V86h1l3.3,4.2V86h1.2v6.4h-1L18.3,88.2z"/>
|
||||
<path class="st1" d="M26.8,92.4c-0.5,0-0.9-0.1-1.2-0.3s-0.7-0.4-1-0.7c-0.3-0.3-0.5-0.6-0.6-1c-0.1-0.4-0.2-0.8-0.2-1.2
|
||||
c0-0.4,0.1-0.8,0.2-1.2s0.4-0.7,0.6-1c0.3-0.3,0.6-0.5,1-0.7s0.8-0.3,1.2-0.3c0.5,0,0.9,0.1,1.2,0.3s0.7,0.4,1,0.7
|
||||
c0.3,0.3,0.5,0.7,0.6,1c0.1,0.4,0.2,0.8,0.2,1.2c0,0.4-0.1,0.8-0.2,1.2c-0.2,0.4-0.4,0.7-0.6,1c-0.3,0.3-0.6,0.5-1,0.7
|
||||
C27.7,92.3,27.2,92.4,26.8,92.4z M25,89.1c0,0.3,0,0.5,0.1,0.8c0.1,0.3,0.2,0.5,0.4,0.7c0.2,0.2,0.3,0.4,0.6,0.5s0.5,0.2,0.8,0.2
|
||||
c0.3,0,0.5-0.1,0.8-0.2s0.4-0.3,0.6-0.5c0.1-0.2,0.3-0.4,0.3-0.7s0.1-0.5,0.1-0.8c0-0.3,0-0.5-0.1-0.8c-0.1-0.3-0.2-0.5-0.4-0.7
|
||||
c-0.2-0.2-0.3-0.4-0.6-0.5c-0.2-0.1-0.5-0.2-0.7-0.2c-0.3,0-0.5,0.1-0.8,0.2s-0.4,0.3-0.6,0.5c-0.2,0.2-0.3,0.4-0.3,0.7
|
||||
C25.1,88.6,25,88.9,25,89.1z"/>
|
||||
<path class="st1" d="M31,92.3V86h2.8c0.3,0,0.6,0.1,0.8,0.2s0.5,0.3,0.6,0.5c0.2,0.2,0.3,0.4,0.4,0.7c0.1,0.3,0.2,0.5,0.2,0.8
|
||||
c0,0.4-0.1,0.8-0.3,1.1c-0.2,0.3-0.5,0.6-0.8,0.7l1.5,2.4h-1.4l-1.3-2.1h-1.2v2.1H31z M32.3,89.1h1.6c0.1,0,0.2,0,0.3-0.1
|
||||
c0.1-0.1,0.2-0.1,0.3-0.2c0.1-0.1,0.1-0.2,0.2-0.3s0.1-0.3,0.1-0.4c0-0.1,0-0.3-0.1-0.4s-0.1-0.2-0.2-0.3c-0.1-0.1-0.2-0.2-0.3-0.2
|
||||
C34,87.1,33.9,87,33.8,87h-1.5V89.1z"/>
|
||||
<path class="st1" d="M41.8,91.3v1.1h-4.4V86h4.4V87h-3.1v1.5h2.7v1h-2.7v1.7H41.8z"/>
|
||||
</g>
|
||||
<path class="st3" d="M27.1,29.7c-0.8,0.2-1.6,0.3-2.4,0.3c-5.4,0-9.8-4.4-9.8-9.8c0-5.4,4.4-9.8,9.8-9.8c5.4,0,9.8,4.4,9.8,9.8
|
||||
c0,0.1,0,0.2,0,0.3c0.4-0.4,0.8-0.6,1.4-0.9C35.6,13.7,30.7,9,24.7,9c-6.2,0-11.2,5-11.2,11.2c0,6.2,5,11.2,11.2,11.2
|
||||
c1,0,2-0.2,3-0.4C27.5,30.5,27.3,30.1,27.1,29.7z"/>
|
||||
<path class="st3" d="M31.2,16.6L31.2,16.6c-0.3-0.3-0.5-0.3-0.8-0.2c0,0-4,0.5-5.7,0.5c0,0-0.1,0-0.1,0c-1.7,0-5.8-0.6-5.8-0.6
|
||||
c-0.3-0.1-0.6,0-0.8,0.3l-0.1,0.2c-0.1,0.2-0.1,0.4-0.1,0.7c0.1,0.2,0.2,0.4,0.4,0.5c0.7,0.3,3.3,1.2,4,1.4c0.1,0,0.4,0.1,0.4,0.6
|
||||
c0,0.6-0.2,3.1-0.5,4.3c-0.3,1.2-0.8,2.7-0.8,2.7c-0.1,0.4,0.1,0.9,0.5,1l0.5,0.2c0.2,0.1,0.4,0.1,0.6,0c0.2-0.1,0.3-0.3,0.4-0.5
|
||||
l1.4-4.3l1.3,4.4c0.1,0.2,0.2,0.4,0.4,0.5c0.1,0.1,0.2,0.1,0.3,0.1c0.1,0,0.2,0,0.3-0.1l0.5-0.2c0.4-0.1,0.6-0.5,0.5-0.9
|
||||
c0,0-0.4-1.6-0.7-2.9c-0.2-0.8-0.3-2-0.3-2.9c0-0.6-0.1-1.1-0.1-1.5c0-0.2,0.1-0.4,0.4-0.5c0.1,0,3.7-1.3,3.7-1.3
|
||||
c0.3-0.1,0.4-0.3,0.5-0.6C31.4,17.1,31.4,16.8,31.2,16.6z"/>
|
||||
<circle class="st3" cx="24.7" cy="14.1" r="1.7"/>
|
||||
<g>
|
||||
<polygon class="st3" points="36.4,30.1 36.4,30.1 36.4,30.1 "/>
|
||||
<path class="st3" d="M35.2,25.8l2.7-2.7c0.4-0.4,0.4-1.1,0-1.5c-0.4-0.4-1.1-0.4-1.5,0l-2.7,2.7l-2.7-2.7c-0.4-0.4-1.1-0.4-1.5,0
|
||||
c-0.4,0.4-0.4,1.1,0,1.5l2.7,2.7l-2.7,2.7c-0.4,0.4-0.4,1.1,0,1.5c0.4,0.4,1.1,0.4,1.5,0l2.7-2.7l2.7,2.7c0.4,0.4,1.1,0.4,1.5,0
|
||||
c0.4-0.4,0.4-1.1,0-1.5L35.2,25.8z"/>
|
||||
</g>
|
||||
<path class="st1" d="M27.3,129.6c-0.8,0.2-1.6,0.3-2.4,0.3c-5.4,0-9.8-4.4-9.8-9.8c0-5.4,4.4-9.8,9.8-9.8c5.4,0,9.8,4.4,9.8,9.8
|
||||
c0,0.1,0,0.2,0,0.3c0.4-0.4,0.8-0.6,1.4-0.9c-0.3-5.9-5.2-10.6-11.2-10.6c-6.2,0-11.2,5-11.2,11.2c0,6.2,5,11.2,11.2,11.2
|
||||
c1,0,2-0.2,3-0.4C27.6,130.5,27.4,130.1,27.3,129.6z"/>
|
||||
<path class="st1" d="M31.3,116.6L31.3,116.6c-0.3-0.3-0.5-0.3-0.8-0.2c0,0-4,0.5-5.7,0.5c0,0-0.1,0-0.1,0c-1.7,0-5.8-0.6-5.8-0.6
|
||||
c-0.3-0.1-0.6,0-0.8,0.3l-0.1,0.2c-0.1,0.2-0.1,0.4-0.1,0.7c0.1,0.2,0.2,0.4,0.4,0.5c0.7,0.3,3.3,1.2,4,1.4c0.1,0,0.4,0.1,0.4,0.6
|
||||
c0,0.6-0.2,3.1-0.5,4.3c-0.3,1.2-0.8,2.7-0.8,2.7c-0.1,0.4,0.1,0.9,0.5,1l0.5,0.2c0.2,0.1,0.4,0.1,0.6,0c0.2-0.1,0.3-0.3,0.4-0.5
|
||||
l1.4-4.3l1.3,4.4c0.1,0.2,0.2,0.4,0.4,0.5c0.1,0.1,0.2,0.1,0.3,0.1c0.1,0,0.2,0,0.3-0.1l0.5-0.2c0.4-0.1,0.6-0.5,0.5-0.9
|
||||
c0,0-0.4-1.6-0.7-2.9c-0.2-0.8-0.3-2-0.3-2.9c0-0.6-0.1-1.1-0.1-1.5c0-0.2,0.1-0.4,0.4-0.5c0.1,0,3.7-1.3,3.7-1.3
|
||||
c0.3-0.1,0.4-0.3,0.5-0.6C31.6,117,31.5,116.8,31.3,116.6z"/>
|
||||
<circle class="st1" cx="24.9" cy="114.1" r="1.7"/>
|
||||
<g>
|
||||
<path class="st1" d="M7.8,142.5v-6.4H9v6.4H7.8z"/>
|
||||
<path class="st1" d="M14.9,141.7c-0.5,0.5-1.1,0.8-1.7,0.8c-0.4,0-0.8-0.1-1.2-0.3c-0.4-0.2-0.7-0.4-0.9-0.7
|
||||
c-0.3-0.3-0.5-0.7-0.6-1s-0.2-0.8-0.2-1.2c0-0.4,0.1-0.9,0.2-1.2c0.2-0.4,0.4-0.7,0.6-1c0.3-0.3,0.6-0.5,1-0.7
|
||||
c0.4-0.2,0.8-0.3,1.2-0.3c0.6,0,1.1,0.1,1.5,0.4s0.7,0.6,0.9,1l-0.9,0.7c-0.2-0.3-0.4-0.6-0.6-0.7s-0.6-0.3-0.9-0.3
|
||||
c-0.3,0-0.5,0.1-0.7,0.2c-0.2,0.1-0.4,0.3-0.5,0.5c-0.2,0.2-0.3,0.4-0.4,0.7c-0.1,0.3-0.1,0.5-0.1,0.8c0,0.3,0,0.6,0.1,0.8
|
||||
c0.1,0.3,0.2,0.5,0.4,0.7c0.2,0.2,0.4,0.3,0.6,0.5c0.2,0.1,0.5,0.2,0.7,0.2c0.6,0,1.1-0.3,1.6-0.9v-0.5h-1.3v-0.9h2.3v3.3h-1V141.7
|
||||
z"/>
|
||||
<path class="st1" d="M18.4,138.4v4.1h-1.2v-6.4h1l3.3,4.2v-4.2h1.2v6.4h-1L18.4,138.4z"/>
|
||||
<path class="st1" d="M26.9,142.5c-0.5,0-0.9-0.1-1.2-0.3s-0.7-0.4-1-0.7c-0.3-0.3-0.5-0.6-0.6-1c-0.1-0.4-0.2-0.8-0.2-1.2
|
||||
c0-0.4,0.1-0.8,0.2-1.2s0.4-0.7,0.6-1c0.3-0.3,0.6-0.5,1-0.7s0.8-0.3,1.2-0.3c0.5,0,0.9,0.1,1.2,0.3s0.7,0.4,1,0.7
|
||||
c0.3,0.3,0.5,0.7,0.6,1c0.1,0.4,0.2,0.8,0.2,1.2c0,0.4-0.1,0.8-0.2,1.2c-0.2,0.4-0.4,0.7-0.6,1c-0.3,0.3-0.6,0.5-1,0.7
|
||||
C27.8,142.4,27.4,142.5,26.9,142.5z M25.2,139.3c0,0.3,0,0.5,0.1,0.8c0.1,0.3,0.2,0.5,0.4,0.7c0.2,0.2,0.3,0.4,0.6,0.5
|
||||
s0.5,0.2,0.8,0.2c0.3,0,0.5-0.1,0.8-0.2s0.4-0.3,0.6-0.5c0.1-0.2,0.3-0.4,0.3-0.7s0.1-0.5,0.1-0.8c0-0.3,0-0.5-0.1-0.8
|
||||
c-0.1-0.3-0.2-0.5-0.4-0.7c-0.2-0.2-0.3-0.4-0.6-0.5c-0.2-0.1-0.5-0.2-0.7-0.2c-0.3,0-0.5,0.1-0.8,0.2s-0.4,0.3-0.6,0.5
|
||||
c-0.2,0.2-0.3,0.4-0.3,0.7C25.2,138.7,25.2,139,25.2,139.3z"/>
|
||||
<path class="st1" d="M31.2,142.5v-6.4H34c0.3,0,0.6,0.1,0.8,0.2s0.5,0.3,0.6,0.5c0.2,0.2,0.3,0.4,0.4,0.7c0.1,0.3,0.2,0.5,0.2,0.8
|
||||
c0,0.4-0.1,0.8-0.3,1.1c-0.2,0.3-0.5,0.6-0.8,0.7l1.5,2.4H35l-1.3-2.1h-1.2v2.1H31.2z M32.4,139.2H34c0.1,0,0.2,0,0.3-0.1
|
||||
c0.1-0.1,0.2-0.1,0.3-0.2c0.1-0.1,0.1-0.2,0.2-0.3s0.1-0.3,0.1-0.4c0-0.1,0-0.3-0.1-0.4s-0.1-0.2-0.2-0.3c-0.1-0.1-0.2-0.2-0.3-0.2
|
||||
c-0.1-0.1-0.2-0.1-0.3-0.1h-1.5V139.2z"/>
|
||||
<path class="st1" d="M41.9,141.4v1.1h-4.4v-6.4h4.4v1.1h-3.1v1.5h2.7v1h-2.7v1.7H41.9z"/>
|
||||
</g>
|
||||
<g>
|
||||
<polygon class="st1" points="36.5,130.1 36.5,130.1 36.5,130.1 "/>
|
||||
<path class="st1" d="M35.3,125.8l2.7-2.7c0.4-0.4,0.4-1.1,0-1.5c-0.4-0.4-1.1-0.4-1.5,0l-2.7,2.7l-2.7-2.7c-0.4-0.4-1.1-0.4-1.5,0
|
||||
c-0.4,0.4-0.4,1.1,0,1.5l2.7,2.7l-2.7,2.7c-0.4,0.4-0.4,1.1,0,1.5c0.4,0.4,1.1,0.4,1.5,0l2.7-2.7l2.7,2.7c0.4,0.4,1.1,0.4,1.5,0
|
||||
c0.4-0.4,0.4-1.1,0-1.5L35.3,125.8z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st1" d="M7.7,192.7v-6.4h1.2v6.4H7.7z"/>
|
||||
<path class="st1" d="M14.7,192c-0.5,0.5-1.1,0.8-1.7,0.8c-0.4,0-0.8-0.1-1.2-0.3c-0.4-0.2-0.7-0.4-0.9-0.7c-0.3-0.3-0.5-0.7-0.6-1
|
||||
s-0.2-0.8-0.2-1.2c0-0.4,0.1-0.9,0.2-1.2c0.2-0.4,0.4-0.7,0.6-1c0.3-0.3,0.6-0.5,1-0.7c0.4-0.2,0.8-0.3,1.2-0.3
|
||||
c0.6,0,1.1,0.1,1.5,0.4s0.7,0.6,0.9,1l-0.9,0.7c-0.2-0.3-0.4-0.6-0.6-0.7s-0.6-0.3-0.9-0.3c-0.3,0-0.5,0.1-0.7,0.2
|
||||
c-0.2,0.1-0.4,0.3-0.5,0.5c-0.2,0.2-0.3,0.4-0.4,0.7c-0.1,0.3-0.1,0.5-0.1,0.8c0,0.3,0,0.6,0.1,0.8c0.1,0.3,0.2,0.5,0.4,0.7
|
||||
c0.2,0.2,0.4,0.3,0.6,0.5c0.2,0.1,0.5,0.2,0.7,0.2c0.6,0,1.1-0.3,1.6-0.9v-0.5h-1.3v-0.9h2.3v3.3h-1V192z"/>
|
||||
<path class="st1" d="M18.3,188.6v4.1h-1.2v-6.4h1l3.3,4.2v-4.2h1.2v6.4h-1L18.3,188.6z"/>
|
||||
<path class="st1" d="M26.8,192.7c-0.5,0-0.9-0.1-1.2-0.3s-0.7-0.4-1-0.7c-0.3-0.3-0.5-0.6-0.6-1c-0.1-0.4-0.2-0.8-0.2-1.2
|
||||
c0-0.4,0.1-0.8,0.2-1.2s0.4-0.7,0.6-1c0.3-0.3,0.6-0.5,1-0.7s0.8-0.3,1.2-0.3c0.5,0,0.9,0.1,1.2,0.3s0.7,0.4,1,0.7
|
||||
c0.3,0.3,0.5,0.7,0.6,1c0.1,0.4,0.2,0.8,0.2,1.2c0,0.4-0.1,0.8-0.2,1.2c-0.2,0.4-0.4,0.7-0.6,1c-0.3,0.3-0.6,0.5-1,0.7
|
||||
C27.7,192.6,27.2,192.7,26.8,192.7z M25,189.5c0,0.3,0,0.5,0.1,0.8c0.1,0.3,0.2,0.5,0.4,0.7c0.2,0.2,0.3,0.4,0.6,0.5
|
||||
s0.5,0.2,0.8,0.2c0.3,0,0.5-0.1,0.8-0.2s0.4-0.3,0.6-0.5c0.1-0.2,0.3-0.4,0.3-0.7s0.1-0.5,0.1-0.8c0-0.3,0-0.5-0.1-0.8
|
||||
c-0.1-0.3-0.2-0.5-0.4-0.7c-0.2-0.2-0.3-0.4-0.6-0.5c-0.2-0.1-0.5-0.2-0.7-0.2c-0.3,0-0.5,0.1-0.8,0.2s-0.4,0.3-0.6,0.5
|
||||
c-0.2,0.2-0.3,0.4-0.3,0.7C25.1,189,25,189.2,25,189.5z"/>
|
||||
<path class="st1" d="M31,192.7v-6.4h2.8c0.3,0,0.6,0.1,0.8,0.2s0.5,0.3,0.6,0.5c0.2,0.2,0.3,0.4,0.4,0.7c0.1,0.3,0.2,0.5,0.2,0.8
|
||||
c0,0.4-0.1,0.8-0.3,1.1c-0.2,0.3-0.5,0.6-0.8,0.7l1.5,2.4h-1.4l-1.3-2.1h-1.2v2.1H31z M32.3,189.5h1.6c0.1,0,0.2,0,0.3-0.1
|
||||
c0.1-0.1,0.2-0.1,0.3-0.2c0.1-0.1,0.1-0.2,0.2-0.3s0.1-0.3,0.1-0.4c0-0.1,0-0.3-0.1-0.4s-0.1-0.2-0.2-0.3c-0.1-0.1-0.2-0.2-0.3-0.2
|
||||
c-0.1-0.1-0.2-0.1-0.3-0.1h-1.5V189.5z"/>
|
||||
<path class="st1" d="M41.8,191.6v1.1h-4.4v-6.4h4.4v1.1h-3.1v1.5h2.7v1h-2.7v1.7H41.8z"/>
|
||||
</g>
|
||||
<path class="st1" d="M27.1,179.9c-0.8,0.2-1.6,0.3-2.4,0.3c-5.4,0-9.8-4.4-9.8-9.8c0-5.4,4.4-9.8,9.8-9.8c5.4,0,9.8,4.4,9.8,9.8
|
||||
c0,0.1,0,0.2,0,0.3c0.4-0.4,0.8-0.6,1.4-0.9c-0.3-5.9-5.2-10.6-11.2-10.6c-6.2,0-11.2,5-11.2,11.2c0,6.2,5,11.2,11.2,11.2
|
||||
c1,0,2-0.2,3-0.4C27.5,180.7,27.3,180.3,27.1,179.9z"/>
|
||||
<path class="st1" d="M31.2,166.8L31.2,166.8c-0.3-0.3-0.5-0.3-0.8-0.2c0,0-4,0.5-5.7,0.5c0,0-0.1,0-0.1,0c-1.7,0-5.8-0.6-5.8-0.6
|
||||
c-0.3-0.1-0.6,0-0.8,0.3l-0.1,0.2c-0.1,0.2-0.1,0.4-0.1,0.7c0.1,0.2,0.2,0.4,0.4,0.5c0.7,0.3,3.3,1.2,4,1.4c0.1,0,0.4,0.1,0.4,0.6
|
||||
c0,0.6-0.2,3.1-0.5,4.3c-0.3,1.2-0.8,2.7-0.8,2.7c-0.1,0.4,0.1,0.9,0.5,1l0.5,0.2c0.2,0.1,0.4,0.1,0.6,0c0.2-0.1,0.3-0.3,0.4-0.5
|
||||
l1.4-4.3l1.3,4.4c0.1,0.2,0.2,0.4,0.4,0.5c0.1,0.1,0.2,0.1,0.3,0.1c0.1,0,0.2,0,0.3-0.1l0.5-0.2c0.4-0.1,0.6-0.5,0.5-0.9
|
||||
c0,0-0.4-1.6-0.7-2.9c-0.2-0.8-0.3-2-0.3-2.9c0-0.6-0.1-1.1-0.1-1.5c0-0.2,0.1-0.4,0.4-0.5c0.1,0,3.7-1.3,3.7-1.3
|
||||
c0.3-0.1,0.4-0.3,0.5-0.6C31.4,167.3,31.4,167,31.2,166.8z"/>
|
||||
<circle class="st1" cx="24.7" cy="164.3" r="1.7"/>
|
||||
<g>
|
||||
<polygon class="st1" points="36.4,180.3 36.4,180.3 36.4,180.3 "/>
|
||||
<path class="st1" d="M35.2,176l2.7-2.7c0.4-0.4,0.4-1.1,0-1.5c-0.4-0.4-1.1-0.4-1.5,0l-2.7,2.7l-2.7-2.7c-0.4-0.4-1.1-0.4-1.5,0
|
||||
c-0.4,0.4-0.4,1.1,0,1.5l2.7,2.7l-2.7,2.7c-0.4,0.4-0.4,1.1,0,1.5c0.4,0.4,1.1,0.4,1.5,0l2.7-2.7l2.7,2.7c0.4,0.4,1.1,0.4,1.5,0
|
||||
c0.4-0.4,0.4-1.1,0-1.5L35.2,176z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 14 KiB |
140
interface/resources/icons/tablet-icons/kick.svg
Normal file
|
@ -0,0 +1,140 @@
|
|||
<?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 200.1" style="enable-background:new 0 0 50 200.1;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#414042;}
|
||||
.st1{fill:#FFFFFF;}
|
||||
.st2{fill:#1E1E1E;}
|
||||
.st3{fill:#333333;}
|
||||
</style>
|
||||
<g id="Layer_2">
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M50.1,146.1c0,2.2-1.8,4-4,4h-42c-2.2,0-4-1.8-4-4v-42c0-2.2,1.8-4,4-4h42c2.2,0,4,1.8,4,4V146.1z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M50,196.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,4V196.1z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st1" d="M50,46c0,2.2-1.8,4-4,4H4c-2.2,0-4-1.8-4-4V4c0-2.2,1.8-4,4-4h42c2.2,0,4,1.8,4,4V46z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st2" d="M50,96.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,4V96.1z"/>
|
||||
</g>
|
||||
</g>
|
||||
<path class="st1" d="M24.7,81.2c-6.2,0-11.2-5-11.2-11.2c0-6.2,5-11.2,11.2-11.2c6.2,0,11.2,5,11.2,11.2
|
||||
C35.9,76.2,30.9,81.2,24.7,81.2z M24.7,60.3c-5.4,0-9.8,4.4-9.8,9.8c0,5.4,4.4,9.8,9.8,9.8c5.4,0,9.8-4.4,9.8-9.8
|
||||
C34.5,64.6,30.1,60.3,24.7,60.3z"/>
|
||||
<path class="st1" d="M27.1,79.5c-0.8,0.2-1.6,0.3-2.4,0.3c-5.4,0-9.8-4.4-9.8-9.8c0-5.4,4.4-9.8,9.8-9.8c5.4,0,9.8,4.4,9.8,9.8
|
||||
c0,0.1,0,0.2,0,0.3c0.4-0.4,0.8-0.6,1.4-0.9c-0.3-5.9-5.2-10.6-11.2-10.6c-6.2,0-11.2,5-11.2,11.2c0,6.2,5,11.2,11.2,11.2
|
||||
c1,0,2-0.2,3-0.4C27.5,80.4,27.3,80,27.1,79.5z"/>
|
||||
<path class="st1" d="M31.2,66.5L31.2,66.5c-0.3-0.3-0.5-0.3-0.8-0.2c0,0-4,0.5-5.7,0.5c0,0-0.1,0-0.1,0c-1.7,0-5.8-0.6-5.8-0.6
|
||||
c-0.3-0.1-0.6,0-0.8,0.3l-0.1,0.2c-0.1,0.2-0.1,0.4-0.1,0.7c0.1,0.2,0.2,0.4,0.4,0.5c0.7,0.3,3.3,1.2,4,1.4c0.1,0,0.4,0.1,0.4,0.6
|
||||
c0,0.6-0.2,3.1-0.5,4.3c-0.3,1.2-0.8,2.7-0.8,2.7c-0.1,0.4,0.1,0.9,0.5,1l0.5,0.2c0.2,0.1,0.4,0.1,0.6,0c0.2-0.1,0.3-0.3,0.4-0.5
|
||||
l1.4-4.3l1.3,4.4c0.1,0.2,0.2,0.4,0.4,0.5c0.1,0.1,0.2,0.1,0.3,0.1c0.1,0,0.2,0,0.3-0.1l0.5-0.2c0.4-0.1,0.6-0.5,0.5-0.9
|
||||
c0,0-0.4-1.6-0.7-2.9c-0.2-0.8-0.3-2-0.3-2.9c0-0.6-0.1-1.1-0.1-1.5c0-0.2,0.1-0.4,0.4-0.5c0.1,0,3.7-1.3,3.7-1.3
|
||||
c0.3-0.1,0.4-0.3,0.5-0.6C31.4,66.9,31.4,66.7,31.2,66.5z"/>
|
||||
<circle class="st1" cx="24.7" cy="64" r="1.7"/>
|
||||
<path class="st3" d="M27.1,29.7c-0.8,0.2-1.6,0.3-2.4,0.3c-5.4,0-9.8-4.4-9.8-9.8c0-5.4,4.4-9.8,9.8-9.8c5.4,0,9.8,4.4,9.8,9.8
|
||||
c0,0.1,0,0.2,0,0.3c0.4-0.4,0.8-0.6,1.4-0.9C35.6,13.7,30.7,9,24.7,9c-6.2,0-11.2,5-11.2,11.2c0,6.2,5,11.2,11.2,11.2
|
||||
c1,0,2-0.2,3-0.4C27.5,30.5,27.3,30.1,27.1,29.7z"/>
|
||||
<path class="st3" d="M31.2,16.6L31.2,16.6c-0.3-0.3-0.5-0.3-0.8-0.2c0,0-4,0.5-5.7,0.5c0,0-0.1,0-0.1,0c-1.7,0-5.8-0.6-5.8-0.6
|
||||
c-0.3-0.1-0.6,0-0.8,0.3l-0.1,0.2c-0.1,0.2-0.1,0.4-0.1,0.7c0.1,0.2,0.2,0.4,0.4,0.5c0.7,0.3,3.3,1.2,4,1.4c0.1,0,0.4,0.1,0.4,0.6
|
||||
c0,0.6-0.2,3.1-0.5,4.3c-0.3,1.2-0.8,2.7-0.8,2.7c-0.1,0.4,0.1,0.9,0.5,1l0.5,0.2c0.2,0.1,0.4,0.1,0.6,0c0.2-0.1,0.3-0.3,0.4-0.5
|
||||
l1.4-4.3l1.3,4.4c0.1,0.2,0.2,0.4,0.4,0.5c0.1,0.1,0.2,0.1,0.3,0.1c0.1,0,0.2,0,0.3-0.1l0.5-0.2c0.4-0.1,0.6-0.5,0.5-0.9
|
||||
c0,0-0.4-1.6-0.7-2.9c-0.2-0.8-0.3-2-0.3-2.9c0-0.6-0.1-1.1-0.1-1.5c0-0.2,0.1-0.4,0.4-0.5c0.1,0,3.7-1.3,3.7-1.3
|
||||
c0.3-0.1,0.4-0.3,0.5-0.6C31.4,17.1,31.4,16.8,31.2,16.6z"/>
|
||||
<circle class="st3" cx="24.7" cy="14.1" r="1.7"/>
|
||||
<g>
|
||||
<polygon class="st3" points="36.4,30.1 36.4,30.1 36.4,30.1 "/>
|
||||
<path class="st3" d="M35.2,25.8l2.7-2.7c0.4-0.4,0.4-1.1,0-1.5c-0.4-0.4-1.1-0.4-1.5,0l-2.7,2.7l-2.7-2.7c-0.4-0.4-1.1-0.4-1.5,0
|
||||
c-0.4,0.4-0.4,1.1,0,1.5l2.7,2.7l-2.7,2.7c-0.4,0.4-0.4,1.1,0,1.5c0.4,0.4,1.1,0.4,1.5,0l2.7-2.7l2.7,2.7c0.4,0.4,1.1,0.4,1.5,0
|
||||
c0.4-0.4,0.4-1.1,0-1.5L35.2,25.8z"/>
|
||||
</g>
|
||||
<path class="st1" d="M27.3,129.6c-0.8,0.2-1.6,0.3-2.4,0.3c-5.4,0-9.8-4.4-9.8-9.8c0-5.4,4.4-9.8,9.8-9.8c5.4,0,9.8,4.4,9.8,9.8
|
||||
c0,0.1,0,0.2,0,0.3c0.4-0.4,0.8-0.6,1.4-0.9c-0.3-5.9-5.2-10.6-11.2-10.6c-6.2,0-11.2,5-11.2,11.2c0,6.2,5,11.2,11.2,11.2
|
||||
c1,0,2-0.2,3-0.4C27.6,130.5,27.4,130.1,27.3,129.6z"/>
|
||||
<path class="st1" d="M31.3,116.6L31.3,116.6c-0.3-0.3-0.5-0.3-0.8-0.2c0,0-4,0.5-5.7,0.5c0,0-0.1,0-0.1,0c-1.7,0-5.8-0.6-5.8-0.6
|
||||
c-0.3-0.1-0.6,0-0.8,0.3l-0.1,0.2c-0.1,0.2-0.1,0.4-0.1,0.7c0.1,0.2,0.2,0.4,0.4,0.5c0.7,0.3,3.3,1.2,4,1.4c0.1,0,0.4,0.1,0.4,0.6
|
||||
c0,0.6-0.2,3.1-0.5,4.3c-0.3,1.2-0.8,2.7-0.8,2.7c-0.1,0.4,0.1,0.9,0.5,1l0.5,0.2c0.2,0.1,0.4,0.1,0.6,0c0.2-0.1,0.3-0.3,0.4-0.5
|
||||
l1.4-4.3l1.3,4.4c0.1,0.2,0.2,0.4,0.4,0.5c0.1,0.1,0.2,0.1,0.3,0.1c0.1,0,0.2,0,0.3-0.1l0.5-0.2c0.4-0.1,0.6-0.5,0.5-0.9
|
||||
c0,0-0.4-1.6-0.7-2.9c-0.2-0.8-0.3-2-0.3-2.9c0-0.6-0.1-1.1-0.1-1.5c0-0.2,0.1-0.4,0.4-0.5c0.1,0,3.7-1.3,3.7-1.3
|
||||
c0.3-0.1,0.4-0.3,0.5-0.6C31.6,117,31.5,116.8,31.3,116.6z"/>
|
||||
<circle class="st1" cx="24.9" cy="114.1" r="1.7"/>
|
||||
<g>
|
||||
<polygon class="st1" points="36.5,130.1 36.5,130.1 36.5,130.1 "/>
|
||||
<path class="st1" d="M35.3,125.8l2.7-2.7c0.4-0.4,0.4-1.1,0-1.5c-0.4-0.4-1.1-0.4-1.5,0l-2.7,2.7l-2.7-2.7c-0.4-0.4-1.1-0.4-1.5,0
|
||||
c-0.4,0.4-0.4,1.1,0,1.5l2.7,2.7l-2.7,2.7c-0.4,0.4-0.4,1.1,0,1.5c0.4,0.4,1.1,0.4,1.5,0l2.7-2.7l2.7,2.7c0.4,0.4,1.1,0.4,1.5,0
|
||||
c0.4-0.4,0.4-1.1,0-1.5L35.3,125.8z"/>
|
||||
</g>
|
||||
<path class="st1" d="M27.1,179.9c-0.8,0.2-1.6,0.3-2.4,0.3c-5.4,0-9.8-4.4-9.8-9.8c0-5.4,4.4-9.8,9.8-9.8c5.4,0,9.8,4.4,9.8,9.8
|
||||
c0,0.1,0,0.2,0,0.3c0.4-0.4,0.8-0.6,1.4-0.9c-0.3-5.9-5.2-10.6-11.2-10.6c-6.2,0-11.2,5-11.2,11.2c0,6.2,5,11.2,11.2,11.2
|
||||
c1,0,2-0.2,3-0.4C27.5,180.7,27.3,180.3,27.1,179.9z"/>
|
||||
<path class="st1" d="M31.2,166.8L31.2,166.8c-0.3-0.3-0.5-0.3-0.8-0.2c0,0-4,0.5-5.7,0.5c0,0-0.1,0-0.1,0c-1.7,0-5.8-0.6-5.8-0.6
|
||||
c-0.3-0.1-0.6,0-0.8,0.3l-0.1,0.2c-0.1,0.2-0.1,0.4-0.1,0.7c0.1,0.2,0.2,0.4,0.4,0.5c0.7,0.3,3.3,1.2,4,1.4c0.1,0,0.4,0.1,0.4,0.6
|
||||
c0,0.6-0.2,3.1-0.5,4.3c-0.3,1.2-0.8,2.7-0.8,2.7c-0.1,0.4,0.1,0.9,0.5,1l0.5,0.2c0.2,0.1,0.4,0.1,0.6,0c0.2-0.1,0.3-0.3,0.4-0.5
|
||||
l1.4-4.3l1.3,4.4c0.1,0.2,0.2,0.4,0.4,0.5c0.1,0.1,0.2,0.1,0.3,0.1c0.1,0,0.2,0,0.3-0.1l0.5-0.2c0.4-0.1,0.6-0.5,0.5-0.9
|
||||
c0,0-0.4-1.6-0.7-2.9c-0.2-0.8-0.3-2-0.3-2.9c0-0.6-0.1-1.1-0.1-1.5c0-0.2,0.1-0.4,0.4-0.5c0.1,0,3.7-1.3,3.7-1.3
|
||||
c0.3-0.1,0.4-0.3,0.5-0.6C31.4,167.3,31.4,167,31.2,166.8z"/>
|
||||
<circle class="st1" cx="24.7" cy="164.3" r="1.7"/>
|
||||
<g>
|
||||
<polygon class="st1" points="36.4,180.3 36.4,180.3 36.4,180.3 "/>
|
||||
<path class="st1" d="M35.2,176l2.7-2.7c0.4-0.4,0.4-1.1,0-1.5c-0.4-0.4-1.1-0.4-1.5,0l-2.7,2.7l-2.7-2.7c-0.4-0.4-1.1-0.4-1.5,0
|
||||
c-0.4,0.4-0.4,1.1,0,1.5l2.7,2.7l-2.7,2.7c-0.4,0.4-0.4,1.1,0,1.5c0.4,0.4,1.1,0.4,1.5,0l2.7-2.7l2.7,2.7c0.4,0.4,1.1,0.4,1.5,0
|
||||
c0.4-0.4,0.4-1.1,0-1.5L35.2,176z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st1" d="M14.7,92.3v-6.4h1.2v3.1l2.8-3.1H20l-2.4,2.8l2.6,3.6h-1.3l-2-2.9l-0.9,0.9v1.9H14.7z"/>
|
||||
<path class="st1" d="M20.9,92.3v-6.4h1.2v6.4H20.9z"/>
|
||||
<path class="st1" d="M23.3,89.1c0-0.4,0.1-0.8,0.2-1.2c0.1-0.4,0.3-0.7,0.6-1c0.3-0.3,0.6-0.5,1-0.7s0.8-0.3,1.3-0.3
|
||||
c0.6,0,1.1,0.1,1.5,0.4c0.4,0.3,0.7,0.6,0.9,1l-1,0.7c-0.1-0.2-0.2-0.3-0.3-0.5c-0.1-0.1-0.2-0.2-0.4-0.3c-0.1-0.1-0.3-0.1-0.4-0.2
|
||||
c-0.1,0-0.3,0-0.4,0c-0.3,0-0.6,0.1-0.8,0.2s-0.4,0.3-0.6,0.5c-0.1,0.2-0.3,0.4-0.3,0.7s-0.1,0.5-0.1,0.8c0,0.3,0,0.6,0.1,0.8
|
||||
c0.1,0.3,0.2,0.5,0.4,0.7c0.2,0.2,0.3,0.4,0.6,0.5c0.2,0.1,0.5,0.2,0.7,0.2c0.1,0,0.3,0,0.4-0.1c0.1,0,0.3-0.1,0.4-0.2
|
||||
c0.1-0.1,0.3-0.2,0.4-0.3c0.1-0.1,0.2-0.3,0.3-0.4l1,0.6c-0.1,0.2-0.2,0.5-0.4,0.6c-0.2,0.2-0.4,0.3-0.6,0.5
|
||||
c-0.2,0.1-0.5,0.2-0.7,0.3c-0.3,0.1-0.5,0.1-0.8,0.1c-0.4,0-0.9-0.1-1.2-0.3c-0.4-0.2-0.7-0.4-1-0.8c-0.3-0.3-0.5-0.7-0.6-1.1
|
||||
C23.4,89.9,23.3,89.5,23.3,89.1z"/>
|
||||
<path class="st1" d="M30,92.3v-6.4h1.2v3.1l2.8-3.1h1.3l-2.4,2.8l2.6,3.6h-1.3l-2-2.9l-0.9,0.9v1.9H30z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st3" d="M14.7,42.6v-6.4h1.2v3.1l2.8-3.1H20l-2.4,2.8l2.6,3.6h-1.3l-2-2.9l-0.9,0.9v1.9H14.7z"/>
|
||||
<path class="st3" d="M20.9,42.6v-6.4h1.2v6.4H20.9z"/>
|
||||
<path class="st3" d="M23.3,39.4c0-0.4,0.1-0.8,0.2-1.2c0.1-0.4,0.3-0.7,0.6-1c0.3-0.3,0.6-0.5,1-0.7s0.8-0.3,1.3-0.3
|
||||
c0.6,0,1.1,0.1,1.5,0.4c0.4,0.3,0.7,0.6,0.9,1l-1,0.7c-0.1-0.2-0.2-0.3-0.3-0.5c-0.1-0.1-0.2-0.2-0.4-0.3c-0.1-0.1-0.3-0.1-0.4-0.2
|
||||
c-0.1,0-0.3,0-0.4,0c-0.3,0-0.6,0.1-0.8,0.2S25.2,37.8,25,38c-0.1,0.2-0.3,0.4-0.3,0.7s-0.1,0.5-0.1,0.8c0,0.3,0,0.6,0.1,0.8
|
||||
c0.1,0.3,0.2,0.5,0.4,0.7c0.2,0.2,0.3,0.4,0.6,0.5c0.2,0.1,0.5,0.2,0.7,0.2c0.1,0,0.3,0,0.4-0.1c0.1,0,0.3-0.1,0.4-0.2
|
||||
c0.1-0.1,0.3-0.2,0.4-0.3c0.1-0.1,0.2-0.3,0.3-0.4l1,0.6c-0.1,0.2-0.2,0.5-0.4,0.6c-0.2,0.2-0.4,0.3-0.6,0.5
|
||||
c-0.2,0.1-0.5,0.2-0.7,0.3c-0.3,0.1-0.5,0.1-0.8,0.1c-0.4,0-0.9-0.1-1.2-0.3c-0.4-0.2-0.7-0.4-1-0.8c-0.3-0.3-0.5-0.7-0.6-1.1
|
||||
C23.4,40.2,23.3,39.8,23.3,39.4z"/>
|
||||
<path class="st3" d="M30,42.6v-6.4h1.2v3.1l2.8-3.1h1.3l-2.4,2.8l2.6,3.6h-1.3l-2-2.9l-0.9,0.9v1.9H30z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st1" d="M14.8,142.4v-6.4H16v3.1l2.8-3.1h1.3l-2.4,2.8l2.6,3.6h-1.3l-2-2.9l-0.9,0.9v1.9H14.8z"/>
|
||||
<path class="st1" d="M21,142.4v-6.4h1.2v6.4H21z"/>
|
||||
<path class="st1" d="M23.4,139.2c0-0.4,0.1-0.8,0.2-1.2c0.1-0.4,0.3-0.7,0.6-1c0.3-0.3,0.6-0.5,1-0.7c0.4-0.2,0.8-0.3,1.3-0.3
|
||||
c0.6,0,1.1,0.1,1.5,0.4c0.4,0.3,0.7,0.6,0.9,1l-1,0.7c-0.1-0.2-0.2-0.3-0.3-0.5c-0.1-0.1-0.2-0.2-0.4-0.3c-0.1-0.1-0.3-0.1-0.4-0.2
|
||||
c-0.1,0-0.3,0-0.4,0c-0.3,0-0.6,0.1-0.8,0.2s-0.4,0.3-0.6,0.5c-0.1,0.2-0.3,0.4-0.3,0.7s-0.1,0.5-0.1,0.8c0,0.3,0,0.6,0.1,0.8
|
||||
c0.1,0.3,0.2,0.5,0.4,0.7c0.2,0.2,0.3,0.4,0.6,0.5c0.2,0.1,0.5,0.2,0.7,0.2c0.1,0,0.3,0,0.4-0.1c0.1,0,0.3-0.1,0.4-0.2
|
||||
s0.3-0.2,0.4-0.3c0.1-0.1,0.2-0.3,0.3-0.4l1,0.6c-0.1,0.2-0.2,0.5-0.4,0.6c-0.2,0.2-0.4,0.3-0.6,0.5c-0.2,0.1-0.5,0.2-0.7,0.3
|
||||
c-0.3,0.1-0.5,0.1-0.8,0.1c-0.4,0-0.9-0.1-1.2-0.3c-0.4-0.2-0.7-0.4-1-0.8c-0.3-0.3-0.5-0.7-0.6-1.1
|
||||
C23.5,140,23.4,139.6,23.4,139.2z"/>
|
||||
<path class="st1" d="M30.1,142.4v-6.4h1.2v3.1l2.8-3.1h1.3l-2.4,2.8l2.6,3.6h-1.3l-2-2.9l-0.9,0.9v1.9H30.1z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st1" d="M14.7,192.7v-6.4h1.2v3.1l2.8-3.1H20l-2.4,2.8l2.6,3.6h-1.3l-2-2.9l-0.9,0.9v1.9H14.7z"/>
|
||||
<path class="st1" d="M20.9,192.7v-6.4h1.2v6.4H20.9z"/>
|
||||
<path class="st1" d="M23.3,189.4c0-0.4,0.1-0.8,0.2-1.2c0.1-0.4,0.3-0.7,0.6-1c0.3-0.3,0.6-0.5,1-0.7c0.4-0.2,0.8-0.3,1.3-0.3
|
||||
c0.6,0,1.1,0.1,1.5,0.4s0.7,0.6,0.9,1l-1,0.7c-0.1-0.2-0.2-0.3-0.3-0.5c-0.1-0.1-0.2-0.2-0.4-0.3s-0.3-0.1-0.4-0.2
|
||||
c-0.1,0-0.3,0-0.4,0c-0.3,0-0.6,0.1-0.8,0.2s-0.4,0.3-0.6,0.5c-0.1,0.2-0.3,0.4-0.3,0.7s-0.1,0.5-0.1,0.8c0,0.3,0,0.6,0.1,0.8
|
||||
c0.1,0.3,0.2,0.5,0.4,0.7c0.2,0.2,0.3,0.4,0.6,0.5c0.2,0.1,0.5,0.2,0.7,0.2c0.1,0,0.3,0,0.4-0.1c0.1,0,0.3-0.1,0.4-0.2
|
||||
c0.1-0.1,0.3-0.2,0.4-0.3c0.1-0.1,0.2-0.3,0.3-0.4l1,0.6c-0.1,0.2-0.2,0.5-0.4,0.6c-0.2,0.2-0.4,0.3-0.6,0.5
|
||||
c-0.2,0.1-0.5,0.2-0.7,0.3c-0.3,0.1-0.5,0.1-0.8,0.1c-0.4,0-0.9-0.1-1.2-0.3c-0.4-0.2-0.7-0.4-1-0.8c-0.3-0.3-0.5-0.7-0.6-1.1
|
||||
C23.4,190.2,23.3,189.8,23.3,189.4z"/>
|
||||
<path class="st1" d="M29.9,192.7v-6.4h1.2v3.1l2.8-3.1h1.3l-2.4,2.8l2.6,3.6h-1.3l-2-2.9l-0.9,0.9v1.9H29.9z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 10 KiB |
23
interface/resources/icons/tablet-icons/market-i.svg
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?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" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g id="Layer_2">
|
||||
</g>
|
||||
<g id="Layer_1">
|
||||
<g>
|
||||
<path class="st0" d="M45.4,13.7c-1.6-0.1-3.2,0-4.8,0c-7.5,0-18,0-25.7,0c-0.6-1.8-1-3.5-1.7-5.2C13,7.9,12.2,7.1,11.5,7
|
||||
C9.4,6.7,7.3,6.7,5.2,6.7c-1.1,0-1.9,0.5-1.9,1.7c0,1.2,0.8,1.7,1.9,1.7c1.3,0,2.7,0,4,0.1c0.5,0.1,1.2,0.6,1.4,1.1
|
||||
c0.9,2.6,1.7,5.2,2.5,7.8c1.3,4.3,1.8,5.5,3.1,10.2c0.6,2.3,1.2,2.8,2.2,3.3c1.1,0.4,2.1,0.4,2.1,0.4h1.8c4.6,0,12.2,0,16.8,0
|
||||
c1.1,0,2.1-0.1,2.6-1.4c1.9-5.1,3.8-10.2,5.7-15.3C47.8,14.7,47.1,13.8,45.4,13.7z M38.9,28.7c-0.1,0.3-0.8,0.7-1.2,0.7
|
||||
c-4.6,0-12.2,0-16.8,0c-0.4,0-1.1-0.3-1.2-0.7c-1.3-3.8-2.4-7.6-3.7-11.5h27.1C41.8,21.2,40.4,24.9,38.9,28.7z"/>
|
||||
<path class="st0" d="M17.2,37.3L17.2,37.3c-1,0-1.7,0.2-2.2,0.7c-0.6,0.6-0.9,1.3-0.9,2.3c0,1,0.3,1.8,0.8,2.4
|
||||
c0.6,0.6,1.4,0.9,2.2,0.9c1.8,0,3.2-1.4,3.2-3.2c0-0.9-0.3-1.7-0.9-2.3C18.8,37.6,18,37.3,17.2,37.3z"/>
|
||||
<path class="st0" d="M35.1,37.3L35.1,37.3c-1,0-1.7,0.2-2.2,0.7c-0.6,0.6-0.9,1.4-0.9,2.4c0,1,0.3,1.8,0.8,2.4
|
||||
c0.6,0.6,1.3,0.9,2.2,0.9c1.8,0,3.2-1.5,3.2-3.3c0-0.9-0.3-1.6-0.9-2.2C36.8,37.6,36,37.3,35.1,37.3z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
17
interface/resources/icons/tablet-icons/menu-i.svg
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?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" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g id="Layer_2">
|
||||
</g>
|
||||
<g id="Layer_1">
|
||||
<path class="st0" d="M37,18.5H14.1c-0.9,0-1.7-0.7-1.7-1.7c0-0.9,0.8-1.7,1.7-1.7H37c0.9,0,1.7,0.7,1.7,1.7
|
||||
C38.6,17.8,37.9,18.5,37,18.5z"/>
|
||||
<path class="st0" d="M37,28H14.1c-0.9,0-1.7-0.8-1.7-1.7c0-0.9,0.8-1.7,1.7-1.7H37c0.9,0,1.7,0.8,1.7,1.7C38.6,27.3,37.9,28,37,28z
|
||||
"/>
|
||||
<path class="st0" d="M37,37.4H14.1c-0.9,0-1.7-0.8-1.7-1.7s0.8-1.7,1.7-1.7H37c0.9,0,1.7,0.8,1.7,1.7S37.9,37.4,37,37.4z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 835 B |
21
interface/resources/icons/tablet-icons/mic-a.svg
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?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" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g id="Layer_2">
|
||||
</g>
|
||||
<g id="Layer_1">
|
||||
<path class="st0" d="M28.9,17.1v-0.5c0-2-1.7-3.6-3.7-3.6h0c-2,0-3.7,1.6-3.7,3.6v6.9L28.9,17.1z"/>
|
||||
<path class="st0" d="M21.5,29.2v0.2c0,2,1.6,3.6,3.7,3.6h0c2,0,3.7-1.6,3.7-3.6v-6.6L21.5,29.2z"/>
|
||||
<path class="st0" d="M39.1,16.8L13.6,39.1c-0.7,0.6-1.8,0.5-2.4-0.2l-0.2-0.2c-0.6-0.7-0.5-1.8,0.2-2.4l25.4-22.4
|
||||
c0.7-0.6,1.8-0.5,2.4,0.2l0.2,0.2C39.8,15.1,39.7,16.1,39.1,16.8z"/>
|
||||
<path class="st0" d="M23.4,40.2l0,3.4h-4.3c-1,0-1.8,0.8-1.8,1.8c0,1,0.8,1.8,1.8,1.8h12.3c1,0,1.8-0.8,1.8-1.8
|
||||
c0-1-0.8-1.8-1.8-1.8h-4.4l0-3.4c5.2-0.8,9.2-5,9.2-10.1c0-0.1,0-5.1,0-5.3c0-1-0.9-1.7-1.8-1.7c-1,0-1.7,0.8-1.7,1.8
|
||||
c0,0.3,0,4.8,0,5.2c0,3.7-3.4,6.7-7.5,6.7c-3.6,0-6.7-2.3-7.3-5.4L15,34C16.4,37.2,19.6,39.7,23.4,40.2z"/>
|
||||
<path class="st0" d="M17.7,24.9c0-1-0.7-1.8-1.6-1.8c-1-0.1-1.8,0.7-1.9,1.6c0,0.2,0,4.2,0,5.3l3.5-3.1
|
||||
C17.7,25.9,17.7,25,17.7,24.9z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
22
interface/resources/icons/tablet-icons/mic-i.svg
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?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" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g id="Layer_2">
|
||||
</g>
|
||||
<g id="Layer_1">
|
||||
<path class="st0" d="M31.4,14.1l2.2-2.2c-2.1-2.5-5.3-4.1-8.8-4.1c-3.4,0-6.4,1.5-8.5,3.8c0.7,0.7,1.5,1.5,2.2,2.2
|
||||
c1.6-1.7,3.8-2.9,6.3-2.9C27.5,10.9,29.9,12.1,31.4,14.1z"/>
|
||||
<path class="st0" d="M36.5,9l2.2-2.2C35.3,3,30.3,0.6,24.8,0.6c-5.3,0-10.2,2.3-13.6,5.9c0.7,0.7,1.5,1.5,2.2,2.2
|
||||
c2.9-3,6.9-5,11.4-5C29.5,3.7,33.6,5.8,36.5,9z"/>
|
||||
<path class="st0" d="M28.5,22.7v-4.4c0-2-1.6-3.6-3.7-3.6h0c-2,0-3.7,1.6-3.7,3.6v4.4H28.5z"/>
|
||||
<path class="st0" d="M21.1,26.5v4.3c0,2,1.7,3.6,3.7,3.6h0c2,0,3.7-1.6,3.7-3.6v-4.3H21.1z"/>
|
||||
<path class="st0" d="M36,31.5c0-0.1,0-5.1,0-5.3c0-1-0.9-1.7-1.8-1.7c-1,0-1.7,0.8-1.7,1.8c0,0.3,0,4.8,0,5.2
|
||||
c0,3.7-3.4,6.7-7.5,6.7c-4.1,0-7.5-3-7.5-6.7c0-0.4,0-4.9,0-5.3c0-1-0.7-1.8-1.6-1.8C14.9,24.3,14,25,14,26c0,0.3,0,5.4,0,5.5
|
||||
c0,5.1,4,9.3,9.2,10.1l0,3.4h-4.3c-1,0-1.8,0.8-1.8,1.8c0,1,0.8,1.8,1.8,1.8h12.3c1,0,1.8-0.8,1.8-1.8c0-1-0.8-1.8-1.8-1.8h-4.4
|
||||
l0-3.4C32,40.7,36,36.6,36,31.5z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
22
interface/resources/icons/tablet-icons/people-i.svg
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?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" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g id="Layer_2">
|
||||
</g>
|
||||
<g id="Layer_1">
|
||||
<circle class="st0" cx="25.6" cy="14.8" r="8.1"/>
|
||||
<path class="st0" d="M31.4,27h-11c-4.6,0-8.2,3.9-8.2,8.5v4.3c3.8,2.8,8.1,4.5,13.1,4.5c5.6,0,10.6-2.1,14.4-5.6v-3.2
|
||||
C39.6,30.9,35.9,27,31.4,27z"/>
|
||||
<circle class="st0" cx="41.6" cy="17.1" r="3.5"/>
|
||||
<path class="st0" d="M43.9,23.9h-4.1c-0.9,0-1.7,0.4-2.3,1c1.2,0.6,2.3,1.6,3.1,2.5c1,1.2,1.5,2.7,1.7,4.3c0.3,0.9,0.4,1.8,0.2,2.8
|
||||
v0.6c1.6-2.2,3.3-6,4-8.1v-0.3C46.5,25.7,45.3,23.9,43.9,23.9z"/>
|
||||
<circle class="st0" cx="9.4" cy="18" r="3.5"/>
|
||||
<path class="st0" d="M8.5,35.7c-0.1-0.7-0.1-1.4,0-2.1l0.1-0.2c0-0.2,0-0.4,0-0.6c0-0.2,0-0.3,0.1-0.5c0-0.2,0-0.3,0-0.5
|
||||
c0.2-2.1,1.6-4.4,3.2-5.7c0.4-0.4,0.9-0.6,1.4-0.9c-0.5-0.5-1.3-0.7-2-0.7H7c-1.4,0-2.6,1.8-2.4,2.7v0.3
|
||||
C5.1,29.8,6.8,33.5,8.5,35.7z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
21
interface/resources/icons/tablet-icons/scripts-i.svg
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?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" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g id="Layer_2">
|
||||
</g>
|
||||
<g id="Layer_1">
|
||||
<g>
|
||||
<path class="st0" d="M29.1,45H12c-3.5,0-6.4-1.8-8.4-5.1c-1.4-2.4-1.9-4.7-1.9-4.8l-0.3-1.6h10.4V4.8h35.8v27
|
||||
c0.1,0.8,0.5,6.6-2.4,10c-1.4,1.6-3.3,2.4-5.6,2.4c-4.8,0-7.2-2.6-8.3-4.7c-0.6-1.2-1-2.3-1.1-3.2H5c0.3,0.7,0.6,1.5,1.1,2.3
|
||||
c1.5,2.4,3.5,3.6,5.9,3.6h17.1c0.8,0,1.4,0.6,1.4,1.4C30.5,44.3,29.9,45,29.1,45z M14.6,33.5h18l0.1,1.3c0,0,0.1,1.7,1,3.4
|
||||
c1.2,2.1,3.1,3.2,5.8,3.2c1.5,0,2.7-0.5,3.5-1.4c1.2-1.3,1.6-3.3,1.7-4.8c0.2-1.7,0-3.1,0-3.1l0-0.1l0-0.1V7.6H14.6V33.5z"/>
|
||||
<path class="st0" d="M39.6,14.3H19.7c-0.8,0-1.5-0.7-1.5-1.5s0.7-1.5,1.5-1.5h19.9c0.8,0,1.5,0.7,1.5,1.5S40.4,14.3,39.6,14.3z"/>
|
||||
<path class="st0" d="M39.6,21.3H19.7c-0.8,0-1.5-0.7-1.5-1.6s0.7-1.6,1.5-1.6h19.9c0.8,0,1.5,0.7,1.5,1.6S40.4,21.3,39.6,21.3z"/>
|
||||
<path class="st0" d="M39.6,28.1H19.7c-0.8,0-1.5-0.7-1.5-1.6s0.7-1.6,1.5-1.6h19.9c0.8,0,1.5,0.7,1.5,1.6S40.4,28.1,39.6,28.1z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
18
interface/resources/icons/tablet-icons/snap-i.svg
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?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" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g id="Layer_2">
|
||||
</g>
|
||||
<g id="Layer_1">
|
||||
<g>
|
||||
<path class="st0" d="M38.5,16.3h-3.7v-1.6c0-2.4-1.6-3-4-3H18.7c-2.4,0-3.6,0.5-3.6,3v1.6h-3.6c-2.4,0-4.5,1-4.5,3.5V36
|
||||
c0,2.4,1.9,4.6,4.5,4.6h27.1c2.4,0,4.2-2.7,4.2-5.2V19.7C42.8,17.3,41,16.3,38.5,16.3z M25.2,37c-5.5,0-9.9-4.5-9.9-9.9
|
||||
c0-5.5,4.5-9.9,9.9-9.9c5.5,0,9.9,4.5,9.9,9.9S30.8,37,25.2,37z"/>
|
||||
<circle class="st0" cx="25.2" cy="27" r="5.9"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 804 B |
16
interface/resources/icons/tablet-icons/switch-a.svg
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?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" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g id="Layer_2">
|
||||
</g>
|
||||
<g id="Layer_1">
|
||||
<path class="st0" d="M42.2,6.2H8.1c-3.5,0-6.4,3.1-6.4,6.8v18c0,3.8,2.9,6.8,6.4,6.8h15.3v3.5h-8.9c-0.8,0-1.5,0.9-1.5,1.8
|
||||
c0,0.9,0.7,1.8,1.5,1.8h21.9c0.8,0,1.5-0.9,1.5-1.8c0-0.9-0.7-1.8-1.5-1.8h-9.6v-3.4c-0.3,0-0.6,0-0.9,0h16.3
|
||||
c3.5,0,6.4-3.1,6.4-6.8V13C48.6,9.2,45.7,6.2,42.2,6.2z M45.2,31c0,1.7-1.3,3.1-2.9,3.1H8.1c-1.6,0-2.9-1.4-2.9-3.1V13
|
||||
c0-1.7,1.3-3.1,2.9-3.1h34.2c1.6,0,2.9,1.4,2.9,3.1V31z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 842 B |
15
interface/resources/icons/tablet-icons/switch-i.svg
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?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" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g id="Layer_2">
|
||||
</g>
|
||||
<g id="Layer_1">
|
||||
<path class="st0" d="M25.4,28.7c1.7,0,2.8,1.8,3.8,3.6c0.5,0.8,1.6,2.6,2,2.6h7.5c4.2,0,7.6-3.8,7.6-8.4v-5.4
|
||||
c0-4.6-3.4-8.4-7.6-8.4H11.9c-4.2,0-7.6,3.8-7.6,8.4v5.4c0,4.6,3.4,8.4,7.6,8.4h7.3c0.6,0,1.7-1.6,2.4-2.7
|
||||
C22.6,30.5,23.7,28.7,25.4,28.7C25.3,28.7,25.4,28.7,25.4,28.7z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 717 B |
30
interface/resources/icons/tablet-icons/users-i.svg
Normal file
|
@ -0,0 +1,30 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g id="Layer_2">
|
||||
</g>
|
||||
<g id="Layer_1">
|
||||
<g>
|
||||
<path class="st0" d="M22.6,14.8c-0.1-0.9-0.1-1.8,0.1-2.6h-8.6c-0.9,0-1.7,0.8-1.7,1.8s0.8,1.8,1.7,1.8h8.7
|
||||
C22.7,15.4,22.7,15.1,22.6,14.8z"/>
|
||||
<path class="st0" d="M26.5,22.3c-0.6-0.5-1.1-1-1.6-1.6H14.1c-0.9,0-1.7,0.8-1.7,1.8c0,1,0.8,1.8,1.7,1.8h8.2
|
||||
c0.3-0.2,0.6-0.3,0.9-0.4c1.2-0.8,2.5-1.2,3.9-1.3C26.9,22.6,26.7,22.5,26.5,22.3z"/>
|
||||
<path class="st0" d="M17.9,30.3c0.1-0.3,0.1-0.5,0.2-0.7h-4c-0.9,0-1.7,0.8-1.7,1.8c0,1,0.8,1.8,1.7,1.8h3.2
|
||||
C17.3,32.2,17.5,31.2,17.9,30.3z"/>
|
||||
<path class="st0" d="M5.9,15.7H4.5c-0.9,0-1.7-0.8-1.7-1.8s0.8-1.8,1.7-1.8h1.4c0.9,0,1.7,0.8,1.7,1.8S6.8,15.7,5.9,15.7z"/>
|
||||
<path class="st0" d="M5.9,24.4H4.5c-0.9,0-1.7-0.8-1.7-1.8c0-1,0.8-1.8,1.7-1.8h1.4c0.9,0,1.7,0.8,1.7,1.8
|
||||
C7.6,23.6,6.8,24.4,5.9,24.4z"/>
|
||||
<path class="st0" d="M5.9,33.2H4.5c-0.9,0-1.7-0.8-1.7-1.8s0.8-1.8,1.7-1.8h1.4c0.9,0,1.7,0.8,1.7,1.8S6.8,33.2,5.9,33.2z"/>
|
||||
<path class="st0" d="M17.3,38.3h-3.2c-0.9,0-1.7,0.8-1.7,1.8s0.8,1.8,1.7,1.8h3.2V38.3z"/>
|
||||
<path class="st0" d="M5.9,42H4.5c-0.9,0-1.7-0.8-1.7-1.8c0-1,0.8-1.8,1.7-1.8h1.4c0.9,0,1.7,0.8,1.7,1.8C7.6,41.1,6.8,42,5.9,42z"
|
||||
/>
|
||||
<circle class="st0" cx="34.5" cy="14.3" r="7.8"/>
|
||||
<path class="st0" d="M23.9,44.1c10.4,0,19.5-4.4,23.3-13.4c-1.3-2.7-3.9-4.7-7.1-4.7H29.5c-4.4,0-8.2,3.8-8.2,8.3v9.7
|
||||
C22.2,44,23,44.1,23.9,44.1z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
18
interface/resources/icons/tablet-mute-icon.svg
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?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 40 40" style="enable-background:new 0 0 40 40;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<path class="st0" d="M24.6,12.5l1.6-1.6C24.7,9.2,22.4,8,19.9,8c-2.4,0-4.6,1-6.1,2.7c0.5,0.5,1,1,1.6,1.6c1.1-1.2,2.7-2,4.5-2
|
||||
C21.8,10.2,23.5,11.1,24.6,12.5z"/>
|
||||
<path class="st0" d="M28.3,8.9l1.6-1.6c-2.4-2.7-6-4.4-9.9-4.4c-3.8,0-7.3,1.6-9.7,4.2c0.5,0.5,1,1,1.6,1.6c2-2.2,4.9-3.5,8.1-3.5
|
||||
C23.2,5.1,26.2,6.6,28.3,8.9z"/>
|
||||
<path class="st0" d="M22.5,18.7v-3.1c0-1.5-1.2-2.6-2.6-2.6h0c-1.5,0-2.6,1.1-2.6,2.6v3.1H22.5z"/>
|
||||
<path class="st0" d="M17.3,21.4v3c0,1.5,1.2,2.6,2.7,2.6h0c1.5,0,2.6-1.2,2.6-2.6v-3H17.3z"/>
|
||||
<path class="st0" d="M27.9,24.9c0,0,0-3.6,0-3.8c0-0.7-0.6-1.2-1.3-1.2c-0.7,0-1.2,0.6-1.2,1.3c0,0.2,0,3.4,0,3.7
|
||||
c0,2.6-2.4,4.8-5.3,4.8c-2.9,0-5.3-2.1-5.3-4.8c0-0.3,0-3.5,0-3.8c0-0.7-0.5-1.3-1.2-1.3c-0.7,0-1.3,0.5-1.3,1.2c0,0.2,0,3.9,0,3.9
|
||||
c0,3.6,2.9,6.6,6.6,7.2l0,2.4h-3.1c-0.7,0-1.3,0.6-1.3,1.3c0,0.7,0.6,1.3,1.3,1.3h8.8c0.7,0,1.3-0.6,1.3-1.3c0-0.7-0.6-1.3-1.3-1.3
|
||||
h-3.2l0-2.4C25,31.6,27.9,28.6,27.9,24.9z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
19
interface/resources/icons/tablet-unmute-icon.svg
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?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 40 40" style="enable-background:new 0 0 40 40;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<path class="st0" d="M17.3,18.4v6c0,1.5,1.2,2.6,2.7,2.6h0c1.5,0,2.6-1.2,2.6-2.6v-6H17.3z"/>
|
||||
<path class="st0" d="M27.9,24.9c0,0,0-3.6,0-3.8c0-0.7-0.6-1.2-1.3-1.2c-0.7,0-1.2,0.6-1.2,1.3c0,0.2,0,3.4,0,3.7
|
||||
c0,2.6-2.4,4.8-5.3,4.8c-2.9,0-5.3-2.1-5.3-4.8c0-0.3,0-3.5,0-3.8c0-0.7-0.5-1.3-1.2-1.3c-0.7,0-1.3,0.5-1.3,1.2c0,0.2,0,3.9,0,3.9
|
||||
c0,3.6,2.9,6.6,6.6,7.2l0,2.4h-3.1c-0.7,0-1.3,0.6-1.3,1.3c0,0.7,0.6,1.3,1.3,1.3h8.8c0.7,0,1.3-0.6,1.3-1.3c0-0.7-0.6-1.3-1.3-1.3
|
||||
h-3.2l0-2.4C25,31.6,27.9,28.6,27.9,24.9z"/>
|
||||
<g>
|
||||
<polygon class="st0" points="23.4,15.8 23.4,15.8 23.4,15.8 "/>
|
||||
<path class="st0" d="M21.9,10.5l3.4-3.4c0.5-0.5,0.5-1.4,0-1.9c-0.5-0.5-1.4-0.5-1.9,0L20,8.6l-3.4-3.4c-0.5-0.5-1.4-0.5-1.9,0
|
||||
s-0.5,1.4,0,1.9l3.4,3.4l-3.4,3.4c-0.5,0.5-0.5,1.4,0,1.9c0.5,0.5,1.4,0.5,1.9,0l3.4-3.4l3.4,3.4c0.5,0.5,1.4,0.5,1.9,0
|
||||
c0.5-0.5,0.5-1.4,0-1.9L21.9,10.5z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
BIN
interface/resources/meshes/tablet-home-button.fbx
Normal file
BIN
interface/resources/meshes/tablet-stylus-fat.fbx
Normal file
BIN
interface/resources/meshes/tablet-stylus-skinny.fbx
Normal file
BIN
interface/resources/meshes/tablet-with-home-button.fbx
Normal file
|
@ -187,9 +187,6 @@ Window {
|
|||
ToolbarButton {
|
||||
id: homeButton
|
||||
imageURL: "../images/home.svg"
|
||||
buttonState: 1
|
||||
defaultState: 1
|
||||
hoverState: 2
|
||||
onClicked: {
|
||||
addressBarDialog.loadHome();
|
||||
root.shown = false;
|
||||
|
@ -204,9 +201,6 @@ Window {
|
|||
ToolbarButton {
|
||||
id: backArrow;
|
||||
imageURL: "../images/backward.svg";
|
||||
hoverState: addressBarDialog.backEnabled ? 2 : 0;
|
||||
defaultState: addressBarDialog.backEnabled ? 1 : 0;
|
||||
buttonState: addressBarDialog.backEnabled ? 1 : 0;
|
||||
onClicked: addressBarDialog.loadBack();
|
||||
anchors {
|
||||
left: homeButton.right
|
||||
|
@ -216,9 +210,6 @@ Window {
|
|||
ToolbarButton {
|
||||
id: forwardArrow;
|
||||
imageURL: "../images/forward.svg";
|
||||
hoverState: addressBarDialog.forwardEnabled ? 2 : 0;
|
||||
defaultState: addressBarDialog.forwardEnabled ? 1 : 0;
|
||||
buttonState: addressBarDialog.forwardEnabled ? 1 : 0;
|
||||
onClicked: addressBarDialog.loadForward();
|
||||
anchors {
|
||||
left: backArrow.right
|
||||
|
|
|
@ -12,7 +12,6 @@ import QtQuick 2.5
|
|||
import QtQuick.Controls 1.4
|
||||
|
||||
import "../dialogs"
|
||||
import "../menus"
|
||||
import "../js/Utils.js" as Utils
|
||||
|
||||
// This is our primary 'desktop' object to which all VR dialogs and windows are childed.
|
||||
|
@ -465,32 +464,7 @@ FocusScope {
|
|||
Component { id: fileDialogBuilder; FileDialog { } }
|
||||
function fileDialog(properties) {
|
||||
return fileDialogBuilder.createObject(desktop, properties);
|
||||
}
|
||||
|
||||
MenuMouseHandler { id: menuPopperUpper }
|
||||
function popupMenu(point) {
|
||||
menuPopperUpper.popup(desktop, rootMenu.items, point);
|
||||
}
|
||||
|
||||
function toggleMenu(point) {
|
||||
menuPopperUpper.toggle(desktop, rootMenu.items, point);
|
||||
}
|
||||
|
||||
Keys.onEscapePressed: {
|
||||
if (menuPopperUpper.closeLastMenu()) {
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
event.accepted = false;
|
||||
}
|
||||
|
||||
Keys.onLeftPressed: {
|
||||
if (menuPopperUpper.closeLastMenu()) {
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
event.accepted = false;
|
||||
}
|
||||
}
|
||||
|
||||
function unfocusWindows() {
|
||||
// First find the active focus item, and unfocus it, all the way
|
||||
|
|
|
@ -48,14 +48,7 @@ OriginalDesktop.Desktop {
|
|||
// This used to create sysToolbar dynamically with a call to getToolbar() within onCompleted.
|
||||
// Beginning with QT 5.6, this stopped working, as anything added to toolbars too early got
|
||||
// wiped during startup.
|
||||
Toolbar {
|
||||
id: sysToolbar;
|
||||
objectName: "com.highfidelity.interface.toolbar.system";
|
||||
anchors.horizontalCenter: settings.constrainToolbarToCenterX ? desktop.horizontalCenter : undefined;
|
||||
// Literal 50 is overwritten by settings from previous session, and sysToolbar.x comes from settings when not constrained.
|
||||
x: sysToolbar.x
|
||||
y: 50
|
||||
}
|
||||
|
||||
Settings {
|
||||
id: settings;
|
||||
category: "toolbar";
|
||||
|
@ -65,7 +58,6 @@ OriginalDesktop.Desktop {
|
|||
settings.constrainToolbarToCenterX = constrain;
|
||||
}
|
||||
property var toolbars: (function (map) { // answer dictionary preloaded with sysToolbar
|
||||
map[sysToolbar.objectName] = sysToolbar;
|
||||
return map; })({});
|
||||
|
||||
|
||||
|
@ -74,27 +66,6 @@ OriginalDesktop.Desktop {
|
|||
WebEngine.settings.javascriptCanAccessClipboard = false;
|
||||
WebEngine.settings.spatialNavigationEnabled = false;
|
||||
WebEngine.settings.localContentCanAccessRemoteUrls = true;
|
||||
|
||||
[ // Allocate the standard buttons in the correct order. They will get images, etc., via scripts.
|
||||
"hmdToggle", "mute", "pal", "bubble", "help",
|
||||
"hudToggle",
|
||||
"com.highfidelity.interface.system.editButton", "marketplace", "snapshot", "goto"
|
||||
].forEach(function (name) {
|
||||
sysToolbar.addButton({objectName: name});
|
||||
});
|
||||
var toggleHudButton = sysToolbar.findButton("hudToggle");
|
||||
toggleHudButton.imageURL = "../../../icons/hud.svg";
|
||||
toggleHudButton.pinned = true;
|
||||
sysToolbar.updatePinned(); // automatic when adding buttons only IFF button is pinned at creation.
|
||||
|
||||
toggleHudButton.buttonState = Qt.binding(function(){
|
||||
return desktop.pinned ? 1 : 0
|
||||
});
|
||||
toggleHudButton.clicked.connect(function(){
|
||||
console.log("Clicked on hud button")
|
||||
var overlayMenuItem = "Overlays"
|
||||
MenuInterface.setIsOptionChecked(overlayMenuItem, !MenuInterface.isOptionChecked(overlayMenuItem));
|
||||
});
|
||||
}
|
||||
|
||||
// Accept a download through the webview
|
||||
|
|
|
@ -158,8 +158,9 @@ Rectangle {
|
|||
onSortIndicatorOrderChanged: sortModel()
|
||||
|
||||
TableViewColumn {
|
||||
id: displayNameHeader
|
||||
role: "displayName"
|
||||
title: "NAMES"
|
||||
title: table.rowCount + (table.rowCount === 1 ? " NAME" : " NAMES")
|
||||
width: nameCardWidth
|
||||
movable: false
|
||||
resizable: false
|
||||
|
@ -351,6 +352,11 @@ Rectangle {
|
|||
visible: iAmAdmin
|
||||
color: hifi.colors.lightGrayText
|
||||
}
|
||||
TextMetrics {
|
||||
id: displayNameHeaderMetrics
|
||||
text: displayNameHeader.title
|
||||
font: displayNameHeader.font
|
||||
}
|
||||
// This Rectangle refers to the [?] popup button next to "NAMES"
|
||||
Rectangle {
|
||||
color: hifi.colors.tableBackgroundLight
|
||||
|
@ -359,7 +365,7 @@ Rectangle {
|
|||
anchors.left: table.left
|
||||
anchors.top: table.top
|
||||
anchors.topMargin: 1
|
||||
anchors.leftMargin: nameCardWidth/2 + 24
|
||||
anchors.leftMargin: nameCardWidth/2 + displayNameHeaderMetrics.width/2 + 6
|
||||
RalewayRegular {
|
||||
id: helpText
|
||||
text: "[?]"
|
||||
|
|
278
interface/resources/qml/hifi/tablet/Tablet.qml
Normal file
|
@ -0,0 +1,278 @@
|
|||
import QtQuick 2.0
|
||||
import QtGraphicalEffects 1.0
|
||||
import "../../styles-uit"
|
||||
|
||||
Item {
|
||||
id: tablet
|
||||
objectName: "tablet"
|
||||
property double micLevel: 0.8
|
||||
property int rowIndex: 0
|
||||
property int columnIndex: 0
|
||||
property int count: (flowMain.children.length - 1)
|
||||
|
||||
// called by C++ code to keep audio bar updated
|
||||
function setMicLevel(newMicLevel) {
|
||||
tablet.micLevel = newMicLevel;
|
||||
}
|
||||
|
||||
// used to look up a button by its uuid
|
||||
function findButtonIndex(uuid) {
|
||||
if (!uuid) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (var i in flowMain.children) {
|
||||
var child = flowMain.children[i];
|
||||
if (child.uuid === uuid) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// called by C++ code when a button should be added to the tablet
|
||||
function addButtonProxy(properties) {
|
||||
var component = Qt.createComponent("TabletButton.qml");
|
||||
var button = component.createObject(flowMain);
|
||||
|
||||
// copy all properites to button
|
||||
var keys = Object.keys(properties).forEach(function (key) {
|
||||
button[key] = properties[key];
|
||||
});
|
||||
|
||||
// pass a reference to the tabletRoot object to the button.
|
||||
button.tabletRoot = parent.parent;
|
||||
return button;
|
||||
}
|
||||
|
||||
// called by C++ code when a button should be removed from the tablet
|
||||
function removeButtonProxy(properties) {
|
||||
var index = findButtonIndex(properties.uuid);
|
||||
if (index < 0) {
|
||||
console.log("Warning: Tablet.qml could not find button with uuid = " + properties.uuid);
|
||||
} else {
|
||||
flowMain.children[index].destroy();
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: bgTopBar
|
||||
height: 90
|
||||
gradient: Gradient {
|
||||
GradientStop {
|
||||
position: 0
|
||||
color: "#2b2b2b"
|
||||
}
|
||||
|
||||
GradientStop {
|
||||
position: 1
|
||||
color: "#1e1e1e"
|
||||
}
|
||||
}
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 0
|
||||
anchors.topMargin: 0
|
||||
anchors.top: parent.top
|
||||
|
||||
|
||||
Image {
|
||||
id: muteIcon
|
||||
width: 40
|
||||
height: 40
|
||||
source: "../../../icons/tablet-mute-icon.svg"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Item {
|
||||
id: item1
|
||||
width: 170
|
||||
height: 10
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 50
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
Rectangle {
|
||||
id: audioBarBase
|
||||
color: "#333333"
|
||||
radius: 5
|
||||
anchors.fill: parent
|
||||
}
|
||||
Rectangle {
|
||||
id: audioBarMask
|
||||
width: parent.width * tablet.micLevel
|
||||
color: "#333333"
|
||||
radius: 5
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 0
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 0
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 0
|
||||
}
|
||||
LinearGradient {
|
||||
anchors.fill: audioBarMask
|
||||
source: audioBarMask
|
||||
start: Qt.point(0, 0)
|
||||
end: Qt.point(170, 0)
|
||||
gradient: Gradient {
|
||||
GradientStop {
|
||||
position: 0
|
||||
color: "#2c8e72"
|
||||
}
|
||||
GradientStop {
|
||||
position: 0.8
|
||||
color: "#1fc6a6"
|
||||
}
|
||||
GradientStop {
|
||||
position: 0.81
|
||||
color: "#ea4c5f"
|
||||
}
|
||||
GradientStop {
|
||||
position: 1
|
||||
color: "#ea4c5f"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RalewaySemiBold {
|
||||
id: usernameText
|
||||
text: tablet.parent.parent.username
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 20
|
||||
horizontalAlignment: Text.AlignRight
|
||||
font.pixelSize: 20
|
||||
color: "#afafaf"
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: bgMain
|
||||
gradient: Gradient {
|
||||
GradientStop {
|
||||
position: 0
|
||||
color: "#2b2b2b"
|
||||
|
||||
}
|
||||
|
||||
GradientStop {
|
||||
position: 1
|
||||
color: "#0f212e"
|
||||
}
|
||||
}
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 0
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 0
|
||||
anchors.top: bgTopBar.bottom
|
||||
anchors.topMargin: 0
|
||||
|
||||
Flickable {
|
||||
id: flickable
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
contentWidth: parent.width
|
||||
contentHeight: flowMain.childrenRect.height + flowMain.anchors.topMargin + flowMain.anchors.bottomMargin + flowMain.spacing
|
||||
clip: true
|
||||
Flow {
|
||||
id: flowMain
|
||||
spacing: 16
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 30
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 30
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 30
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 30
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "muted state"
|
||||
|
||||
PropertyChanges {
|
||||
target: muteText
|
||||
text: "UNMUTE"
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
target: muteIcon
|
||||
source: "../../../icons/tablet-unmute-icon.svg"
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
target: tablet
|
||||
micLevel: 0
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
function setCurrentItemState(state) {
|
||||
var index = rowIndex + columnIndex;
|
||||
|
||||
if (index >= 0 && index <= count ) {
|
||||
flowMain.children[index].state = state;
|
||||
}
|
||||
}
|
||||
function nextItem() {
|
||||
setCurrentItemState("base state");
|
||||
var nextColumnIndex = (columnIndex + 3 + 1) % 3;
|
||||
var nextIndex = rowIndex + nextColumnIndex;
|
||||
if(nextIndex <= count) {
|
||||
columnIndex = nextColumnIndex;
|
||||
};
|
||||
setCurrentItemState("hover state");
|
||||
}
|
||||
|
||||
function previousItem() {
|
||||
setCurrentItemState("base state");
|
||||
var prevIndex = (columnIndex + 3 - 1) % 3;
|
||||
if((rowIndex + prevIndex) <= count){
|
||||
columnIndex = prevIndex;
|
||||
}
|
||||
setCurrentItemState("hover state");
|
||||
}
|
||||
|
||||
function upItem() {
|
||||
setCurrentItemState("base state");
|
||||
rowIndex = rowIndex - 3;
|
||||
if (rowIndex < 0 ) {
|
||||
rowIndex = (count - (count % 3));
|
||||
var index = rowIndex + columnIndex;
|
||||
if(index > count) {
|
||||
rowIndex = rowIndex - 3;
|
||||
}
|
||||
}
|
||||
setCurrentItemState("hover state");
|
||||
}
|
||||
|
||||
function downItem() {
|
||||
setCurrentItemState("base state");
|
||||
rowIndex = rowIndex + 3;
|
||||
var index = rowIndex + columnIndex;
|
||||
if (index > count ) {
|
||||
rowIndex = 0;
|
||||
}
|
||||
setCurrentItemState("hover state");
|
||||
}
|
||||
|
||||
function selectItem() {
|
||||
flowMain.children[rowIndex + columnIndex].clicked();
|
||||
if (tabletRoot) {
|
||||
tabletRoot.playButtonClickSound();
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onRightPressed: nextItem();
|
||||
Keys.onLeftPressed: previousItem();
|
||||
Keys.onDownPressed: downItem();
|
||||
Keys.onUpPressed: upItem();
|
||||
Keys.onReturnPressed: selectItem();
|
||||
}
|
225
interface/resources/qml/hifi/tablet/TabletButton.qml
Normal file
|
@ -0,0 +1,225 @@
|
|||
import QtQuick 2.0
|
||||
import QtGraphicalEffects 1.0
|
||||
|
||||
Item {
|
||||
id: tabletButton
|
||||
property var uuid;
|
||||
property string text: "EDIT"
|
||||
property string icon: "icons/edit-icon.svg"
|
||||
property string activeText: tabletButton.text
|
||||
property string activeIcon: tabletButton.icon
|
||||
property bool isActive: false
|
||||
property bool inDebugMode: false
|
||||
property bool isEntered: false
|
||||
property var tabletRoot;
|
||||
width: 129
|
||||
height: 129
|
||||
|
||||
signal clicked()
|
||||
|
||||
function changeProperty(key, value) {
|
||||
tabletButton[key] = value;
|
||||
}
|
||||
|
||||
onIsActiveChanged: {
|
||||
if (tabletButton.isEntered) {
|
||||
tabletButton.state = (tabletButton.isActive) ? "hover active state" : "hover sate";
|
||||
} else {
|
||||
tabletButton.state = (tabletButton.isActive) ? "active state" : "base sate";
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: buttonBg
|
||||
color: "#000000"
|
||||
opacity: 0.1
|
||||
radius: 8
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 0
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 0
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 0
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: buttonOutline
|
||||
color: "#00000000"
|
||||
opacity: 0.2
|
||||
radius: 8
|
||||
z: 1
|
||||
border.width: 2
|
||||
border.color: "#ffffff"
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 0
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 0
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 0
|
||||
}
|
||||
|
||||
DropShadow {
|
||||
id: glow
|
||||
visible: false
|
||||
anchors.fill: parent
|
||||
horizontalOffset: 0
|
||||
verticalOffset: 0
|
||||
color: "#ffffff"
|
||||
radius: 20
|
||||
z: -1
|
||||
samples: 41
|
||||
source: buttonOutline
|
||||
}
|
||||
|
||||
Image {
|
||||
id: icon
|
||||
width: 50
|
||||
height: 50
|
||||
visible: false
|
||||
anchors.bottom: text.top
|
||||
anchors.bottomMargin: 5
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
fillMode: Image.Stretch
|
||||
source: "../../../" + tabletButton.icon
|
||||
}
|
||||
|
||||
ColorOverlay {
|
||||
id: iconColorOverlay
|
||||
anchors.fill: icon
|
||||
source: icon
|
||||
color: "#ffffff"
|
||||
}
|
||||
|
||||
Text {
|
||||
id: text
|
||||
color: "#ffffff"
|
||||
text: tabletButton.text
|
||||
font.bold: true
|
||||
font.pixelSize: 18
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 20
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
enabled: true
|
||||
onClicked: {
|
||||
console.log("Tablet Button Clicked!");
|
||||
if (tabletButton.inDebugMode) {
|
||||
if (tabletButton.isActive) {
|
||||
tabletButton.isActive = false;
|
||||
} else {
|
||||
tabletButton.isActive = true;
|
||||
}
|
||||
}
|
||||
tabletButton.clicked();
|
||||
if (tabletRoot) {
|
||||
tabletRoot.playButtonClickSound();
|
||||
}
|
||||
}
|
||||
onEntered: {
|
||||
tabletButton.isEntered = true;
|
||||
if (tabletButton.isActive) {
|
||||
tabletButton.state = "hover active state";
|
||||
} else {
|
||||
tabletButton.state = "hover state";
|
||||
}
|
||||
}
|
||||
onExited: {
|
||||
tabletButton.isEntered = false;
|
||||
if (tabletButton.isActive) {
|
||||
tabletButton.state = "active state";
|
||||
} else {
|
||||
tabletButton.state = "base state";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "hover state"
|
||||
|
||||
PropertyChanges {
|
||||
target: buttonOutline
|
||||
border.color: "#1fc6a6"
|
||||
opacity: 1
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
target: glow
|
||||
visible: true
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "active state"
|
||||
|
||||
PropertyChanges {
|
||||
target: buttonOutline
|
||||
border.color: "#1fc6a6"
|
||||
opacity: 1
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
target: buttonBg
|
||||
color: "#1fc6a6"
|
||||
opacity: 1
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
target: text
|
||||
color: "#333333"
|
||||
text: tabletButton.activeText
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
target: iconColorOverlay
|
||||
color: "#333333"
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
target: icon
|
||||
source: "../../../" + tabletButton.activeIcon
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "hover active state"
|
||||
|
||||
PropertyChanges {
|
||||
target: glow
|
||||
visible: true
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
target: buttonOutline
|
||||
border.color: "#ffffff"
|
||||
opacity: 1
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
target: buttonBg
|
||||
color: "#1fc6a6"
|
||||
opacity: 1
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
target: text
|
||||
color: "#333333"
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
target: iconColorOverlay
|
||||
color: "#333333"
|
||||
}
|
||||
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
101
interface/resources/qml/hifi/tablet/TabletMenu.qml
Normal file
|
@ -0,0 +1,101 @@
|
|||
import QtQuick 2.5
|
||||
import QtGraphicalEffects 1.0
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQml 2.2
|
||||
import "."
|
||||
import "../../styles-uit"
|
||||
|
||||
FocusScope {
|
||||
id: tabletMenu
|
||||
objectName: "tabletMenu"
|
||||
|
||||
width: 480
|
||||
height: 720
|
||||
|
||||
property var rootMenu: Menu { objectName:"rootMenu" }
|
||||
property var point: Qt.point(50, 50)
|
||||
|
||||
TabletMouseHandler { id: menuPopperUpper }
|
||||
|
||||
Rectangle {
|
||||
id: bgNavBar
|
||||
height: 90
|
||||
z: 1
|
||||
gradient: Gradient {
|
||||
GradientStop {
|
||||
position: 0
|
||||
color: "#2b2b2b"
|
||||
}
|
||||
|
||||
GradientStop {
|
||||
position: 1
|
||||
color: "#1e1e1e"
|
||||
}
|
||||
}
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 0
|
||||
anchors.topMargin: 0
|
||||
anchors.top: parent.top
|
||||
|
||||
Image {
|
||||
id: menuRootIcon
|
||||
width: 40
|
||||
height: 40
|
||||
source: "../../../icons/tablet-icons/menu-i.svg"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 15
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onEntered: iconColorOverlay.color = "#1fc6a6";
|
||||
onExited: iconColorOverlay.color = "#ffffff";
|
||||
// navigate back to root level menu
|
||||
onClicked: buildMenu();
|
||||
}
|
||||
}
|
||||
|
||||
ColorOverlay {
|
||||
id: iconColorOverlay
|
||||
anchors.fill: menuRootIcon
|
||||
source: menuRootIcon
|
||||
color: "#34a2c7"
|
||||
}
|
||||
|
||||
RalewayBold {
|
||||
id: breadcrumbText
|
||||
text: "Menu"
|
||||
size: 26
|
||||
color: "#34a2c7"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: menuRootIcon.right
|
||||
anchors.leftMargin: 15
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onEntered: breadcrumbText.color = "#1fc6a6";
|
||||
onExited: breadcrumbText.color = "#34a2c7";
|
||||
// navigate back to parent level menu if there is one
|
||||
onClicked:
|
||||
if (breadcrumbText.text !== "Menu") {
|
||||
menuPopperUpper.closeLastMenu();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function pop() {
|
||||
menuPopperUpper.closeLastMenu();
|
||||
}
|
||||
|
||||
function setRootMenu(menu) {
|
||||
tabletMenu.rootMenu = menu
|
||||
buildMenu()
|
||||
}
|
||||
function buildMenu() {
|
||||
menuPopperUpper.popup(tabletMenu, rootMenu.items)
|
||||
}
|
||||
}
|
|
@ -12,8 +12,8 @@ import QtQuick 2.5
|
|||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
|
||||
import "../controls-uit"
|
||||
import "../styles-uit"
|
||||
import "../../controls-uit"
|
||||
import "../../styles-uit"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
@ -31,7 +31,7 @@ Item {
|
|||
// FIXME: Should use radio buttons if source.exclusiveGroup.
|
||||
anchors {
|
||||
left: parent.left
|
||||
leftMargin: hifi.dimensions.menuPadding.x
|
||||
leftMargin: hifi.dimensions.menuPadding.x + 15
|
||||
top: label.top
|
||||
topMargin: 0
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ Item {
|
|||
|
||||
RalewaySemiBold {
|
||||
id: label
|
||||
size: hifi.fontSizes.rootMenu
|
||||
size: 20
|
||||
font.capitalization: isSubMenu ? Font.MixedCase : Font.AllUppercase
|
||||
anchors.left: check.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
@ -103,7 +103,7 @@ Item {
|
|||
HiFiGlyphs {
|
||||
text: hifi.glyphs.disclosureExpand
|
||||
color: source.enabled ? hifi.colors.baseGrayShadow : hifi.colors.baseGrayShadow25
|
||||
size: 2 * hifi.fontSizes.rootMenuDisclosure
|
||||
size: 70
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: parent.right
|
||||
horizontalAlignment: Text.AlignRight
|
|
@ -12,13 +12,13 @@ import QtQuick 2.5
|
|||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
|
||||
import "../styles-uit"
|
||||
|
||||
import "../../styles-uit"
|
||||
import "."
|
||||
FocusScope {
|
||||
id: root
|
||||
implicitHeight: background.height
|
||||
implicitWidth: background.width
|
||||
|
||||
objectName: "root"
|
||||
property alias currentItem: listView.currentItem
|
||||
property alias model: listView.model
|
||||
property bool isSubMenu: false
|
||||
|
@ -32,15 +32,23 @@ FocusScope {
|
|||
radius: hifi.dimensions.borderRadius
|
||||
border.width: hifi.dimensions.borderWidth
|
||||
border.color: hifi.colors.lightGrayText80
|
||||
color: isSubMenu ? hifi.colors.faintGray : hifi.colors.faintGray80
|
||||
color: hifi.colors.faintGray
|
||||
//color: isSubMenu ? hifi.colors.faintGray : hifi.colors.faintGray80
|
||||
}
|
||||
|
||||
|
||||
ListView {
|
||||
id: listView
|
||||
x: 8; y: 8
|
||||
width: 128
|
||||
height: count * 32
|
||||
x: 0
|
||||
y: 0
|
||||
width: 480
|
||||
height: 720
|
||||
contentWidth: 480
|
||||
contentHeight: 720
|
||||
objectName: "menuList"
|
||||
|
||||
topMargin: hifi.dimensions.menuPadding.y
|
||||
bottomMargin: hifi.dimensions.menuPadding.y
|
||||
onEnabledChanged: recalcSize();
|
||||
onVisibleChanged: recalcSize();
|
||||
onCountChanged: recalcSize();
|
||||
|
@ -57,7 +65,7 @@ FocusScope {
|
|||
color: hifi.colors.white
|
||||
}
|
||||
|
||||
delegate: VrMenuItem {
|
||||
delegate: TabletMenuItem {
|
||||
text: name
|
||||
source: item
|
||||
onImplicitHeightChanged: listView.recalcSize()
|
||||
|
@ -91,26 +99,30 @@ FocusScope {
|
|||
newHeight += currentItem.implicitHeight
|
||||
}
|
||||
}
|
||||
newHeight += 2 * hifi.dimensions.menuPadding.y; // White space at top and bottom.
|
||||
newHeight += hifi.dimensions.menuPadding.y * 2; // White space at top and bottom.
|
||||
if (maxWidth > width) {
|
||||
width = maxWidth;
|
||||
}
|
||||
if (newHeight > height) {
|
||||
height = newHeight
|
||||
if (newHeight > contentHeight) {
|
||||
contentHeight = newHeight;
|
||||
}
|
||||
currentIndex = originalIndex;
|
||||
}
|
||||
|
||||
function previousItem() { currentIndex = (currentIndex + count - 1) % count; }
|
||||
function nextItem() { currentIndex = (currentIndex + count + 1) % count; }
|
||||
function selectCurrentItem() { if (currentIndex != -1) root.selected(currentItem.source); }
|
||||
|
||||
|
||||
Keys.onUpPressed: previousItem();
|
||||
Keys.onDownPressed: nextItem();
|
||||
Keys.onSpacePressed: selectCurrentItem();
|
||||
Keys.onRightPressed: selectCurrentItem();
|
||||
Keys.onReturnPressed: selectCurrentItem();
|
||||
Keys.onLeftPressed: previousPage();
|
||||
}
|
||||
|
||||
function previousItem() { listView.currentIndex = (listView.currentIndex + listView.count - 1) % listView.count; }
|
||||
function nextItem() { listView.currentIndex = (listView.currentIndex + listView.count + 1) % listView.count; }
|
||||
function selectCurrentItem() { if (listView.currentIndex != -1) root.selected(currentItem.source); }
|
||||
function previousPage() { root.parent.pop(); }
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -16,11 +16,11 @@ import "."
|
|||
Item {
|
||||
id: root
|
||||
anchors.fill: parent
|
||||
objectName: "MouseMenuHandlerItem"
|
||||
objectName: "tabletMenuHandlerItem"
|
||||
|
||||
MouseArea {
|
||||
id: menuRoot;
|
||||
objectName: "MouseMenuHandlerMouseArea"
|
||||
objectName: "tabletMenuHandlerMouseArea"
|
||||
anchors.fill: parent
|
||||
enabled: d.topMenu !== null
|
||||
onClicked: {
|
||||
|
@ -34,7 +34,7 @@ Item {
|
|||
property var topMenu: null;
|
||||
property var modelMaker: Component { ListModel { } }
|
||||
property var menuViewMaker: Component {
|
||||
VrMenuView {
|
||||
TabletMenuView {
|
||||
id: subMenu
|
||||
onSelected: d.handleSelection(subMenu, currentItem, item)
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ Item {
|
|||
}
|
||||
|
||||
function toModel(items) {
|
||||
var result = modelMaker.createObject(desktop);
|
||||
var result = modelMaker.createObject(tabletMenu);
|
||||
for (var i = 0; i < items.length; ++i) {
|
||||
var item = items[i];
|
||||
if (!item.visible) continue;
|
||||
|
@ -63,7 +63,9 @@ Item {
|
|||
result.append({"name": item.title, "item": item})
|
||||
break;
|
||||
case MenuItemType.Item:
|
||||
result.append({"name": item.text, "item": item})
|
||||
if (item.text !== "Users Online") {
|
||||
result.append({"name": item.text, "item": item})
|
||||
}
|
||||
break;
|
||||
case MenuItemType.Separator:
|
||||
result.append({"name": "", "item": item})
|
||||
|
@ -80,10 +82,16 @@ Item {
|
|||
if (menuStack.length) {
|
||||
topMenu = menuStack[menuStack.length - 1];
|
||||
topMenu.focus = true;
|
||||
topMenu.forceActiveFocus();
|
||||
// show current menu level on nav bar
|
||||
if (topMenu.objectName === "") {
|
||||
breadcrumbText.text = "Menu";
|
||||
} else {
|
||||
breadcrumbText.text = topMenu.objectName;
|
||||
}
|
||||
} else {
|
||||
breadcrumbText.text = "Menu";
|
||||
topMenu = null;
|
||||
offscreenFlags.navigationFocused = false;
|
||||
menuRoot.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -91,7 +99,7 @@ Item {
|
|||
menuStack.push(newMenu);
|
||||
topMenu = newMenu;
|
||||
topMenu.focus = true;
|
||||
offscreenFlags.navigationFocused = true;
|
||||
topMenu.forceActiveFocus();
|
||||
}
|
||||
|
||||
function clearMenus() {
|
||||
|
@ -118,12 +126,7 @@ Item {
|
|||
function buildMenu(items, targetPosition) {
|
||||
var model = toModel(items);
|
||||
// Menus must be childed to desktop for Z-ordering
|
||||
var newMenu = menuViewMaker.createObject(desktop, { model: model, z: topMenu ? topMenu.z + 1 : desktop.zLevels.menu, isSubMenu: topMenu !== null });
|
||||
if (targetPosition) {
|
||||
newMenu.x = targetPosition.x
|
||||
newMenu.y = targetPosition.y - newMenu.height / 3 * 1
|
||||
}
|
||||
clampMenuPosition(newMenu);
|
||||
var newMenu = menuViewMaker.createObject(tabletMenu, { model: model, isSubMenu: topMenu !== null });
|
||||
pushMenu(newMenu);
|
||||
return newMenu;
|
||||
}
|
||||
|
@ -137,39 +140,36 @@ Item {
|
|||
case MenuItemType.Menu:
|
||||
var target = Qt.vector2d(topMenu.x, topMenu.y).plus(Qt.vector2d(selectedItem.x + 96, selectedItem.y));
|
||||
buildMenu(item.items, target).objectName = item.title;
|
||||
// show current menu level on nav bar
|
||||
breadcrumbText.text = item.title;
|
||||
break;
|
||||
|
||||
case MenuItemType.Item:
|
||||
console.log("Triggering " + item.text)
|
||||
// Don't block waiting for modal dialogs and such that the menu might open.
|
||||
delay.trigger(item);
|
||||
clearMenus();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function popup(parent, items, point) {
|
||||
function popup(parent, items) {
|
||||
d.clearMenus();
|
||||
menuRoot.enabled = true;
|
||||
d.buildMenu(items, point);
|
||||
}
|
||||
|
||||
function toggle(parent, items, point) {
|
||||
if (d.topMenu) {
|
||||
d.clearMenus();
|
||||
return;
|
||||
}
|
||||
popup(parent, items, point);
|
||||
}
|
||||
|
||||
function closeLastMenu() {
|
||||
if (d.menuStack.length) {
|
||||
if (d.menuStack.length > 1) {
|
||||
d.popMenu();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function previousItem() { d.topMenu.previousItem(); }
|
||||
function nextItem() { d.topMenu.nextItem(); }
|
||||
function selectCurrentItem() { d.topMenu.selectCurrentItem(); }
|
||||
function previousPage() { d.topMenu.previousPage(); }
|
||||
|
||||
}
|
63
interface/resources/qml/hifi/tablet/TabletRoot.qml
Normal file
|
@ -0,0 +1,63 @@
|
|||
import QtQuick 2.0
|
||||
import Hifi 1.0
|
||||
|
||||
Item {
|
||||
id: tabletRoot
|
||||
objectName: "tabletRoot"
|
||||
property string username: "Unknown user"
|
||||
property var eventBridge;
|
||||
|
||||
signal showDesktop();
|
||||
|
||||
function loadSource(url) {
|
||||
loader.source = url;
|
||||
}
|
||||
|
||||
function loadWebUrl(url, injectedJavaScriptUrl) {
|
||||
loader.item.url = url;
|
||||
loader.item.scriptURL = injectedJavaScriptUrl;
|
||||
}
|
||||
|
||||
SoundEffect {
|
||||
id: buttonClickSound
|
||||
source: "../../../sounds/Gamemaster-Audio-button-click.wav"
|
||||
}
|
||||
|
||||
function playButtonClickSound() {
|
||||
// Because of the asynchronous nature of initalization, it is possible for this function to be
|
||||
// called before the C++ has set the globalPosition context variable.
|
||||
if (typeof globalPosition !== 'undefined') {
|
||||
buttonClickSound.play(globalPosition);
|
||||
}
|
||||
}
|
||||
|
||||
function setUsername(newUsername) {
|
||||
username = newUsername;
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: loader
|
||||
objectName: "loader"
|
||||
asynchronous: false
|
||||
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
|
||||
onLoaded: {
|
||||
if (loader.item.hasOwnProperty("eventBridge")) {
|
||||
loader.item.eventBridge = eventBridge;
|
||||
|
||||
// Hook up callback for clara.io download from the marketplace.
|
||||
eventBridge.webEventReceived.connect(function (event) {
|
||||
if (event.slice(0, 17) === "CLARA.IO DOWNLOAD") {
|
||||
ApplicationInterface.addAssetToWorldFromURL(event.slice(18));
|
||||
}
|
||||
});
|
||||
}
|
||||
loader.item.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
width: 480
|
||||
height: 720
|
||||
}
|
10
interface/resources/qml/hifi/tablet/TabletWebView.qml
Normal file
|
@ -0,0 +1,10 @@
|
|||
import QtQuick 2.0
|
||||
import QtWebEngine 1.2
|
||||
|
||||
import "../../controls" as Controls
|
||||
|
||||
Controls.WebView {
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -6,7 +6,7 @@ Item {
|
|||
property alias alpha: image.opacity
|
||||
property var subImage;
|
||||
property int yOffset: 0
|
||||
property int buttonState: 0
|
||||
property int buttonState: 1
|
||||
property real size: 50
|
||||
width: size; height: size
|
||||
property bool pinned: false
|
||||
|
|
|
@ -3,11 +3,34 @@ import QtQuick.Controls 1.4
|
|||
|
||||
StateImage {
|
||||
id: button
|
||||
property int hoverState: -1
|
||||
property int defaultState: -1
|
||||
property bool isActive: false
|
||||
property bool isEntered: false
|
||||
|
||||
property int imageOffOut: 1
|
||||
property int imageOffIn: 3
|
||||
property int imageOnOut: 0
|
||||
property int imageOnIn: 2
|
||||
|
||||
signal clicked()
|
||||
|
||||
function changeProperty(key, value) {
|
||||
button[key] = value;
|
||||
}
|
||||
|
||||
function updateState() {
|
||||
if (!button.isEntered && !button.isActive) {
|
||||
buttonState = imageOffOut;
|
||||
} else if (!button.isEntered && button.isActive) {
|
||||
buttonState = imageOnOut;
|
||||
} else if (button.isEntered && !button.isActive) {
|
||||
buttonState = imageOffIn;
|
||||
} else {
|
||||
buttonState = imageOnIn;
|
||||
}
|
||||
}
|
||||
|
||||
onIsActiveChanged: updateState();
|
||||
|
||||
Timer {
|
||||
id: asyncClickSender
|
||||
interval: 10
|
||||
|
@ -22,14 +45,12 @@ StateImage {
|
|||
anchors.fill: parent
|
||||
onClicked: asyncClickSender.start();
|
||||
onEntered: {
|
||||
if (hoverState >= 0) {
|
||||
buttonState = hoverState;
|
||||
}
|
||||
button.isEntered = true;
|
||||
updateState();
|
||||
}
|
||||
onExited: {
|
||||
if (defaultState >= 0) {
|
||||
buttonState = defaultState;
|
||||
}
|
||||
button.isEntered = false;
|
||||
updateState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -156,7 +156,7 @@ Item {
|
|||
readonly property real modalDialogTitleHeight: 40
|
||||
readonly property real controlLineHeight: 28 // Height of spinbox control on 1920 x 1080 monitor
|
||||
readonly property real controlInterlineHeight: 21 // 75% of controlLineHeight
|
||||
readonly property vector2d menuPadding: Qt.vector2d(14, 12)
|
||||
readonly property vector2d menuPadding: Qt.vector2d(14, 102)
|
||||
readonly property real scrollbarBackgroundWidth: 18
|
||||
readonly property real scrollbarHandleWidth: scrollbarBackgroundWidth - 2
|
||||
}
|
||||
|
|
BIN
interface/resources/sounds/Gamemaster-Audio-button-click.wav
Normal file
|
@ -111,6 +111,7 @@
|
|||
#include <ScriptEngines.h>
|
||||
#include <ScriptCache.h>
|
||||
#include <SoundCache.h>
|
||||
#include <TabletScriptingInterface.h>
|
||||
#include <Tooltip.h>
|
||||
#include <udt/PacketHeaders.h>
|
||||
#include <UserActivityLogger.h>
|
||||
|
@ -492,6 +493,7 @@ bool setupEssentials(int& argc, char** argv) {
|
|||
DependencyManager::set<WindowScriptingInterface>();
|
||||
DependencyManager::set<HMDScriptingInterface>();
|
||||
DependencyManager::set<ResourceScriptingInterface>();
|
||||
DependencyManager::set<TabletScriptingInterface>();
|
||||
DependencyManager::set<ToolbarScriptingInterface>();
|
||||
DependencyManager::set<UserActivityLoggerScriptingInterface>();
|
||||
|
||||
|
@ -977,7 +979,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
connect(userInputMapper.data(), &UserInputMapper::actionEvent, [this](int action, float state) {
|
||||
using namespace controller;
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
if (offscreenUi->navigationFocused()) {
|
||||
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
|
||||
{
|
||||
auto actionEnum = static_cast<Action>(action);
|
||||
int key = Qt::Key_unknown;
|
||||
static int lastKey = Qt::Key_unknown;
|
||||
|
@ -1021,25 +1024,28 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
break;
|
||||
}
|
||||
|
||||
if (navAxis) {
|
||||
auto window = tabletScriptingInterface->getTabletWindow();
|
||||
if (navAxis && window) {
|
||||
if (lastKey != Qt::Key_unknown) {
|
||||
QKeyEvent event(QEvent::KeyRelease, lastKey, Qt::NoModifier);
|
||||
sendEvent(offscreenUi->getWindow(), &event);
|
||||
sendEvent(window, &event);
|
||||
lastKey = Qt::Key_unknown;
|
||||
}
|
||||
|
||||
if (key != Qt::Key_unknown) {
|
||||
QKeyEvent event(QEvent::KeyPress, key, Qt::NoModifier);
|
||||
sendEvent(offscreenUi->getWindow(), &event);
|
||||
sendEvent(window, &event);
|
||||
tabletScriptingInterface->processEvent(&event);
|
||||
lastKey = key;
|
||||
}
|
||||
} else if (key != Qt::Key_unknown) {
|
||||
} else if (key != Qt::Key_unknown && window) {
|
||||
if (state) {
|
||||
QKeyEvent event(QEvent::KeyPress, key, Qt::NoModifier);
|
||||
sendEvent(offscreenUi->getWindow(), &event);
|
||||
sendEvent(window, &event);
|
||||
tabletScriptingInterface->processEvent(&event);
|
||||
} else {
|
||||
QKeyEvent event(QEvent::KeyRelease, key, Qt::NoModifier);
|
||||
sendEvent(offscreenUi->getWindow(), &event);
|
||||
sendEvent(window, &event);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -1065,12 +1071,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
DependencyManager::get<AudioClient>()->toggleMute();
|
||||
} else if (action == controller::toInt(controller::Action::CYCLE_CAMERA)) {
|
||||
cycleCamera();
|
||||
} else if (action == controller::toInt(controller::Action::UI_NAV_SELECT)) {
|
||||
if (!offscreenUi->navigationFocused()) {
|
||||
toggleMenuUnderReticle();
|
||||
}
|
||||
} else if (action == controller::toInt(controller::Action::CONTEXT_MENU)) {
|
||||
toggleMenuUnderReticle();
|
||||
toggleTabletUI();
|
||||
} else if (action == controller::toInt(controller::Action::RETICLE_X)) {
|
||||
auto oldPos = getApplicationCompositor().getReticlePosition();
|
||||
getApplicationCompositor().setReticlePosition({ oldPos.x + state, oldPos.y });
|
||||
|
@ -1589,15 +1591,17 @@ QString Application::getUserAgent() {
|
|||
return userAgent;
|
||||
}
|
||||
|
||||
void Application::toggleMenuUnderReticle() const {
|
||||
// In HMD, if the menu is near the mouse but not under it, the reticle can be at a significantly
|
||||
// different depth. When you focus on the menu, the cursor can appear to your crossed eyes as both
|
||||
// on the menu and off.
|
||||
// Even in 2D, it is arguable whether the user would want the menu to be to the side.
|
||||
const float X_LEFT_SHIFT = 50.0;
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
auto reticlePosition = getApplicationCompositor().getReticlePosition();
|
||||
offscreenUi->toggleMenu(QPoint(reticlePosition.x - X_LEFT_SHIFT, reticlePosition.y));
|
||||
uint64_t lastTabletUIToggle { 0 };
|
||||
const uint64_t toggleTabletUILockout { 500000 };
|
||||
void Application::toggleTabletUI() const {
|
||||
uint64_t now = usecTimestampNow();
|
||||
if (now - lastTabletUIToggle < toggleTabletUILockout) {
|
||||
return;
|
||||
}
|
||||
lastTabletUIToggle = now;
|
||||
|
||||
auto HMD = DependencyManager::get<HMDScriptingInterface>();
|
||||
HMD->toggleShouldShowTablet();
|
||||
}
|
||||
|
||||
void Application::checkChangeCursor() {
|
||||
|
@ -2933,10 +2937,6 @@ void Application::keyPressEvent(QKeyEvent* event) {
|
|||
|
||||
|
||||
void Application::keyReleaseEvent(QKeyEvent* event) {
|
||||
if (event->key() == Qt::Key_Alt && _altPressed && hasFocus()) {
|
||||
toggleMenuUnderReticle();
|
||||
}
|
||||
|
||||
_keysPressed.remove(event->key());
|
||||
|
||||
_controllerScriptingInterface->emitKeyReleaseEvent(event); // send events to any registered scripts
|
||||
|
@ -4161,9 +4161,11 @@ void Application::setKeyboardFocusOverlay(unsigned int overlayID) {
|
|||
}
|
||||
_lastAcceptedKeyPress = usecTimestampNow();
|
||||
|
||||
auto size = overlay->getSize() * FOCUS_HIGHLIGHT_EXPANSION_FACTOR;
|
||||
const float OVERLAY_DEPTH = 0.0105f;
|
||||
setKeyboardFocusHighlight(overlay->getPosition(), overlay->getRotation(), glm::vec3(size.x, size.y, OVERLAY_DEPTH));
|
||||
if (overlay->getProperty("showKeyboardFocusHighlight").toBool()) {
|
||||
auto size = overlay->getSize() * FOCUS_HIGHLIGHT_EXPANSION_FACTOR;
|
||||
const float OVERLAY_DEPTH = 0.0105f;
|
||||
setKeyboardFocusHighlight(overlay->getPosition(), overlay->getRotation(), glm::vec3(size.x, size.y, OVERLAY_DEPTH));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4377,6 +4379,10 @@ void Application::update(float deltaTime) {
|
|||
PROFILE_RANGE_EX(simulation_physics, "HarvestChanges", 0xffffff00, (uint64_t)getActiveDisplayPlugin()->presentCount());
|
||||
PerformanceTimer perfTimer("harvestChanges");
|
||||
if (_physicsEngine->hasOutgoingChanges()) {
|
||||
// grab the collision events BEFORE handleOutgoingChanges() because at this point
|
||||
// we have a better idea of which objects we own or should own.
|
||||
auto& collisionEvents = _physicsEngine->getCollisionEvents();
|
||||
|
||||
getEntities()->getTree()->withWriteLock([&] {
|
||||
PerformanceTimer perfTimer("handleOutgoingChanges");
|
||||
const VectorOfMotionStates& outgoingChanges = _physicsEngine->getOutgoingChanges();
|
||||
|
@ -4384,11 +4390,10 @@ void Application::update(float deltaTime) {
|
|||
avatarManager->handleOutgoingChanges(outgoingChanges);
|
||||
});
|
||||
|
||||
auto collisionEvents = _physicsEngine->getCollisionEvents();
|
||||
avatarManager->handleCollisionEvents(collisionEvents);
|
||||
|
||||
if (!_aboutToQuit) {
|
||||
// handleCollisionEvents() AFTER handleOutgoinChanges()
|
||||
PerformanceTimer perfTimer("entities");
|
||||
avatarManager->handleCollisionEvents(collisionEvents);
|
||||
// Collision events (and their scripts) must not be handled when we're locked, above. (That would risk
|
||||
// deadlock.)
|
||||
_entitySimulation->handleCollisionEvents(collisionEvents);
|
||||
|
|
|
@ -484,7 +484,7 @@ private:
|
|||
static void dragEnterEvent(QDragEnterEvent* event);
|
||||
|
||||
void maybeToggleMenuVisible(QMouseEvent* event) const;
|
||||
void toggleMenuUnderReticle() const;
|
||||
void toggleTabletUI() const;
|
||||
|
||||
MainWindow* _window;
|
||||
QElapsedTimer& _sessionRunTimer;
|
||||
|
|
|
@ -160,7 +160,7 @@ Menu::Menu() {
|
|||
audioIO.data(), SLOT(toggleMute()));
|
||||
|
||||
// Audio > Show Level Meter
|
||||
addCheckableActionToQMenuAndActionHash(audioMenu, MenuOption::AudioTools, 0, true);
|
||||
addCheckableActionToQMenuAndActionHash(audioMenu, MenuOption::AudioTools, 0, false);
|
||||
|
||||
|
||||
// Avatar menu ----------------------------------
|
||||
|
|
|
@ -892,6 +892,16 @@ glm::quat Avatar::getAbsoluteJointRotationInObjectFrame(int index) const {
|
|||
Transform controllerRightHandTransform = Transform(getControllerRightHandMatrix());
|
||||
return controllerRightHandTransform.getRotation();
|
||||
}
|
||||
case CAMERA_MATRIX_INDEX: {
|
||||
glm::quat rotation;
|
||||
if (_skeletonModel && _skeletonModel->isActive()) {
|
||||
int headJointIndex = _skeletonModel->getFBXGeometry().headJointIndex;
|
||||
if (headJointIndex >= 0) {
|
||||
_skeletonModel->getAbsoluteJointRotationInRigFrame(headJointIndex, rotation);
|
||||
}
|
||||
}
|
||||
return rotation;
|
||||
}
|
||||
default: {
|
||||
glm::quat rotation;
|
||||
_skeletonModel->getAbsoluteJointRotationInRigFrame(index, rotation);
|
||||
|
@ -918,6 +928,16 @@ glm::vec3 Avatar::getAbsoluteJointTranslationInObjectFrame(int index) const {
|
|||
Transform controllerRightHandTransform = Transform(getControllerRightHandMatrix());
|
||||
return controllerRightHandTransform.getTranslation();
|
||||
}
|
||||
case CAMERA_MATRIX_INDEX: {
|
||||
glm::vec3 translation;
|
||||
if (_skeletonModel && _skeletonModel->isActive()) {
|
||||
int headJointIndex = _skeletonModel->getFBXGeometry().headJointIndex;
|
||||
if (headJointIndex >= 0) {
|
||||
_skeletonModel->getAbsoluteJointTranslationInRigFrame(headJointIndex, translation);
|
||||
}
|
||||
}
|
||||
return translation;
|
||||
}
|
||||
default: {
|
||||
glm::vec3 translation;
|
||||
_skeletonModel->getAbsoluteJointTranslationInRigFrame(index, translation);
|
||||
|
|
|
@ -128,32 +128,44 @@ bool AvatarActionHold::getTarget(float deltaTimeStep, glm::quat& rotation, glm::
|
|||
glm::quat palmRotation;
|
||||
|
||||
if (holdingAvatar->isMyAvatar()) {
|
||||
std::shared_ptr<MyAvatar> myAvatar = avatarManager->getMyAvatar();
|
||||
|
||||
// fetch the hand controller pose
|
||||
controller::Pose pose;
|
||||
if (isRightHand) {
|
||||
pose = avatarManager->getMyAvatar()->getRightHandControllerPoseInWorldFrame();
|
||||
pose = myAvatar->getRightHandControllerPoseInWorldFrame();
|
||||
} else {
|
||||
pose = avatarManager->getMyAvatar()->getLeftHandControllerPoseInWorldFrame();
|
||||
pose = myAvatar->getLeftHandControllerPoseInWorldFrame();
|
||||
}
|
||||
|
||||
if (pose.isValid()) {
|
||||
linearVelocity = pose.getVelocity();
|
||||
angularVelocity = pose.getAngularVelocity();
|
||||
|
||||
if (isRightHand) {
|
||||
pose = avatarManager->getMyAvatar()->getRightHandControllerPoseInAvatarFrame();
|
||||
} else {
|
||||
pose = avatarManager->getMyAvatar()->getLeftHandControllerPoseInAvatarFrame();
|
||||
}
|
||||
}
|
||||
|
||||
if (_ignoreIK && pose.isValid()) {
|
||||
|
||||
// this position/rotation should be the same as the one in scripts/system/libraries/controllers.js
|
||||
// otherwise things will do a little hop when you grab them.
|
||||
|
||||
// if (isRightHand) {
|
||||
// pose = myAvatar->getRightHandControllerPoseInAvatarFrame();
|
||||
// } else {
|
||||
// pose = myAvatar->getLeftHandControllerPoseInAvatarFrame();
|
||||
// }
|
||||
// glm::vec3 camRelPos = pose.getTranslation();
|
||||
// glm::quat camRelRot = pose.getRotation();
|
||||
|
||||
int camRelIndex = isRightHand ?
|
||||
CAMERA_RELATIVE_CONTROLLER_RIGHTHAND_INDEX :
|
||||
CAMERA_RELATIVE_CONTROLLER_LEFTHAND_INDEX;
|
||||
glm::vec3 camRelPos = myAvatar->getAbsoluteJointTranslationInObjectFrame(camRelIndex);
|
||||
glm::quat camRelRot = myAvatar->getAbsoluteJointRotationInObjectFrame(camRelIndex);
|
||||
|
||||
Transform avatarTransform;
|
||||
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||
avatarTransform = myAvatar->getTransform();
|
||||
palmPosition = avatarTransform.transform(pose.getTranslation() / myAvatar->getDomainLimitedScale());
|
||||
palmRotation = avatarTransform.getRotation() * pose.getRotation();
|
||||
palmPosition = avatarTransform.transform(camRelPos / myAvatar->getDomainLimitedScale());
|
||||
palmRotation = avatarTransform.getRotation() * camRelRot;
|
||||
} else {
|
||||
glm::vec3 avatarRigidBodyPosition;
|
||||
glm::quat avatarRigidBodyRotation;
|
||||
|
@ -223,6 +235,11 @@ void AvatarActionHold::doKinematicUpdate(float deltaTimeStep) {
|
|||
qDebug() << "AvatarActionHold::doKinematicUpdate -- no owning entity";
|
||||
return;
|
||||
}
|
||||
if (ownerEntity->getParentID() != QUuid()) {
|
||||
// if the held entity has been given a parent, stop acting on it.
|
||||
return;
|
||||
}
|
||||
|
||||
void* physicsInfo = ownerEntity->getPhysicsInfo();
|
||||
if (!physicsInfo) {
|
||||
qDebug() << "AvatarActionHold::doKinematicUpdate -- no owning physics info";
|
||||
|
|
|
@ -797,8 +797,14 @@ void MyAvatar::saveData() {
|
|||
|
||||
settings.beginWriteArray("avatarEntityData");
|
||||
int avatarEntityIndex = 0;
|
||||
auto hmdInterface = DependencyManager::get<HMDScriptingInterface>();
|
||||
_avatarEntitiesLock.withReadLock([&] {
|
||||
for (auto entityID : _avatarEntityData.keys()) {
|
||||
if (hmdInterface->getCurrentTableUIID() == entityID) {
|
||||
// don't persist the tablet between domains / sessions
|
||||
continue;
|
||||
}
|
||||
|
||||
settings.setArrayIndex(avatarEntityIndex);
|
||||
settings.setValue("id", entityID);
|
||||
settings.setValue("properties", _avatarEntityData.value(entityID));
|
||||
|
@ -2388,6 +2394,13 @@ glm::quat MyAvatar::getAbsoluteJointRotationInObjectFrame(int index) const {
|
|||
glm::mat4 result = computeCameraRelativeHandControllerMatrix(controllerSensorMatrix);
|
||||
return glmExtractRotation(result);
|
||||
}
|
||||
case CAMERA_MATRIX_INDEX: {
|
||||
bool success;
|
||||
Transform avatarTransform;
|
||||
Transform::mult(avatarTransform, getParentTransform(success), getLocalTransform());
|
||||
glm::mat4 invAvatarMat = avatarTransform.getInverseMatrix();
|
||||
return glmExtractRotation(invAvatarMat * qApp->getCamera()->getTransform());
|
||||
}
|
||||
default: {
|
||||
return Avatar::getAbsoluteJointRotationInObjectFrame(index);
|
||||
}
|
||||
|
@ -2414,6 +2427,13 @@ glm::vec3 MyAvatar::getAbsoluteJointTranslationInObjectFrame(int index) const {
|
|||
glm::mat4 result = computeCameraRelativeHandControllerMatrix(controllerSensorMatrix);
|
||||
return extractTranslation(result);
|
||||
}
|
||||
case CAMERA_MATRIX_INDEX: {
|
||||
bool success;
|
||||
Transform avatarTransform;
|
||||
Transform::mult(avatarTransform, getParentTransform(success), getLocalTransform());
|
||||
glm::mat4 invAvatarMat = avatarTransform.getInverseMatrix();
|
||||
return extractTranslation(invAvatarMat * qApp->getCamera()->getTransform());
|
||||
}
|
||||
default: {
|
||||
return Avatar::getAbsoluteJointTranslationInObjectFrame(index);
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include <display-plugins/CompositorHelper.h>
|
||||
#include <OffscreenUi.h>
|
||||
#include <plugins/PluginUtils.h>
|
||||
#include <QUuid>
|
||||
|
||||
#include "Application.h"
|
||||
|
||||
|
@ -76,6 +77,10 @@ bool HMDScriptingInterface::shouldShowHandControllers() const {
|
|||
return _showHandControllersCount > 0;
|
||||
}
|
||||
|
||||
void HMDScriptingInterface::closeTablet() {
|
||||
_showTablet = false;
|
||||
}
|
||||
|
||||
QScriptValue HMDScriptingInterface::getHUDLookAtPosition2D(QScriptContext* context, QScriptEngine* engine) {
|
||||
glm::vec3 hudIntersection;
|
||||
auto instance = DependencyManager::get<HMDScriptingInterface>();
|
||||
|
|
|
@ -28,6 +28,10 @@ class HMDScriptingInterface : public AbstractHMDScriptingInterface, public Depen
|
|||
Q_PROPERTY(glm::vec3 position READ getPosition)
|
||||
Q_PROPERTY(glm::quat orientation READ getOrientation)
|
||||
Q_PROPERTY(bool mounted READ isMounted)
|
||||
Q_PROPERTY(bool showTablet READ getShouldShowTablet)
|
||||
Q_PROPERTY(QUuid tabletID READ getCurrentTableUIID WRITE setCurrentTabletUIID)
|
||||
Q_PROPERTY(unsigned int homeButtonID READ getCurrentHomeButtonUUID WRITE setCurrentHomeButtonUUID)
|
||||
|
||||
|
||||
public:
|
||||
Q_INVOKABLE glm::vec3 calculateRayUICollisionPoint(const glm::vec3& position, const glm::vec3& direction) const;
|
||||
|
@ -53,11 +57,11 @@ public:
|
|||
Q_INVOKABLE void disableExtraLaser() const;
|
||||
|
||||
|
||||
/// Suppress the activation of any on-screen keyboard so that a script operation will
|
||||
/// 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
|
||||
/// 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();
|
||||
|
||||
|
@ -70,6 +74,8 @@ public:
|
|||
// rotate the overlay UI sphere so that it is centered about the the current HMD position and orientation
|
||||
Q_INVOKABLE void centerUI();
|
||||
|
||||
Q_INVOKABLE void closeTablet();
|
||||
|
||||
signals:
|
||||
bool shouldShowHandControllersChanged();
|
||||
|
||||
|
@ -80,10 +86,25 @@ public:
|
|||
|
||||
bool isMounted() const;
|
||||
|
||||
void toggleShouldShowTablet() { _showTablet = !_showTablet; }
|
||||
void setShouldShowTablet(bool value) { _showTablet = value; }
|
||||
bool getShouldShowTablet() const { return _showTablet; }
|
||||
|
||||
void setCurrentTabletUIID(QUuid tabletID) { _tabletUIID = tabletID; }
|
||||
QUuid getCurrentTableUIID() const { return _tabletUIID; }
|
||||
|
||||
void setCurrentHomeButtonUUID(unsigned int homeButtonID) { _homeButtonID = homeButtonID; }
|
||||
unsigned int getCurrentHomeButtonUUID() const { return _homeButtonID; }
|
||||
|
||||
private:
|
||||
bool _showTablet { false };
|
||||
QUuid _tabletUIID; // this is the entityID of the WebEntity which is part of (a child of) the tablet-ui.
|
||||
unsigned int _homeButtonID;
|
||||
QUuid _tabletEntityID;
|
||||
|
||||
// Get the position of the HMD
|
||||
glm::vec3 getPosition() const;
|
||||
|
||||
|
||||
// Get the orientation of the HMD
|
||||
glm::quat getOrientation() const;
|
||||
|
||||
|
|
|
@ -147,3 +147,14 @@ void MenuScriptingInterface::triggerOption(const QString& menuOption) {
|
|||
QMetaObject::invokeMethod(Menu::getInstance(), "triggerOption", Q_ARG(const QString&, menuOption));
|
||||
}
|
||||
|
||||
void MenuScriptingInterface::closeInfoView(const QString& path) {
|
||||
QMetaObject::invokeMethod(Menu::getInstance(), "closeInfoView", Q_ARG(const QString&, path));
|
||||
}
|
||||
|
||||
bool MenuScriptingInterface::isInfoViewVisible(const QString& path) {
|
||||
bool result;
|
||||
QMetaObject::invokeMethod(Menu::getInstance(), "isInfoViewVisible", Qt::BlockingQueuedConnection,
|
||||
Q_RETURN_ARG(bool, result), Q_ARG(const QString&, path));
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -182,6 +182,9 @@ public slots:
|
|||
*/
|
||||
void setMenuEnabled(const QString& menuName, bool isEnabled);
|
||||
|
||||
void closeInfoView(const QString& path);
|
||||
bool isInfoViewVisible(const QString& path);
|
||||
|
||||
signals:
|
||||
/**jsdoc
|
||||
* This is a signal that is emitted when a menu item is clicked.
|
||||
|
|
63
interface/src/scripting/QmlWrapper.h
Normal file
|
@ -0,0 +1,63 @@
|
|||
//
|
||||
// Created by Anthony J. Thibault on 2016-12-12
|
||||
// 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_QmlWrapper_h
|
||||
#define hifi_QmlWrapper_h
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <OffscreenUi.h>
|
||||
#include <DependencyManager.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 };
|
||||
};
|
||||
|
||||
#endif
|
|
@ -8,69 +8,44 @@
|
|||
|
||||
#include "ToolbarScriptingInterface.h"
|
||||
|
||||
|
||||
#include <QtCore/QThread>
|
||||
|
||||
#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 };
|
||||
};
|
||||
|
||||
#include "QmlWrapper.h"
|
||||
|
||||
class ToolbarButtonProxy : public QmlWrapper {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ToolbarButtonProxy(QObject* qmlObject, QObject* parent = nullptr) : QmlWrapper(qmlObject, parent) {
|
||||
std::lock_guard<std::mutex> guard(_mutex);
|
||||
_qmlButton = qobject_cast<QQuickItem*>(qmlObject);
|
||||
connect(qmlObject, SIGNAL(clicked()), this, SIGNAL(clicked()));
|
||||
}
|
||||
|
||||
Q_INVOKABLE void editProperties(QVariantMap properties) {
|
||||
std::lock_guard<std::mutex> guard(_mutex);
|
||||
QVariantMap::const_iterator iter = properties.constBegin();
|
||||
while (iter != properties.constEnd()) {
|
||||
_properties[iter.key()] = iter.value();
|
||||
if (_qmlButton) {
|
||||
// [01/25 14:26:20] [WARNING] [default] QMetaObject::invokeMethod: No such method ToolbarButton_QMLTYPE_195::changeProperty(QVariant,QVariant)
|
||||
|
||||
QMetaObject::invokeMethod(_qmlButton, "changeProperty", Qt::AutoConnection,
|
||||
Q_ARG(QVariant, QVariant(iter.key())), Q_ARG(QVariant, iter.value()));
|
||||
}
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
|
||||
signals:
|
||||
void clicked();
|
||||
|
||||
protected:
|
||||
mutable std::mutex _mutex;
|
||||
QQuickItem* _qmlButton { nullptr };
|
||||
QVariantMap _properties;
|
||||
};
|
||||
|
||||
class ToolbarProxy : public QmlWrapper {
|
||||
|
|
|
@ -58,6 +58,8 @@ WindowScriptingInterface::WindowScriptingInterface() {
|
|||
OffscreenUi::warning("Import SVO Error", "You need to be running edit.js to import entities.");
|
||||
}
|
||||
});
|
||||
|
||||
connect(qApp->getWindow(), &MainWindow::windowGeometryChanged, this, &WindowScriptingInterface::geometryChanged);
|
||||
}
|
||||
|
||||
WindowScriptingInterface::~WindowScriptingInterface() {
|
||||
|
|
|
@ -76,6 +76,9 @@ signals:
|
|||
|
||||
void messageBoxClosed(int id, int button);
|
||||
|
||||
// triggered when window size or position changes
|
||||
void geometryChanged(QRect geometry);
|
||||
|
||||
private:
|
||||
QString getPreviousBrowseLocation() const;
|
||||
void setPreviousBrowseLocation(const QString& location);
|
||||
|
|
|
@ -26,7 +26,8 @@ Base3DOverlay::Base3DOverlay() :
|
|||
_isSolid(DEFAULT_IS_SOLID),
|
||||
_isDashedLine(DEFAULT_IS_DASHED_LINE),
|
||||
_ignoreRayIntersection(false),
|
||||
_drawInFront(false)
|
||||
_drawInFront(false),
|
||||
_isAA(true)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -37,7 +38,8 @@ Base3DOverlay::Base3DOverlay(const Base3DOverlay* base3DOverlay) :
|
|||
_isSolid(base3DOverlay->_isSolid),
|
||||
_isDashedLine(base3DOverlay->_isDashedLine),
|
||||
_ignoreRayIntersection(base3DOverlay->_ignoreRayIntersection),
|
||||
_drawInFront(base3DOverlay->_drawInFront)
|
||||
_drawInFront(base3DOverlay->_drawInFront),
|
||||
_isAA(base3DOverlay->_isAA)
|
||||
{
|
||||
setTransform(base3DOverlay->getTransform());
|
||||
}
|
||||
|
@ -175,6 +177,13 @@ void Base3DOverlay::setProperties(const QVariantMap& originalProperties) {
|
|||
needRenderItemUpdate = true;
|
||||
}
|
||||
|
||||
auto isAA = properties["isAA"];
|
||||
if (isAA.isValid()) {
|
||||
bool value = isAA.toBool();
|
||||
setIsAA(value);
|
||||
needRenderItemUpdate = true;
|
||||
}
|
||||
|
||||
// Communicate changes to the renderItem if needed
|
||||
if (needRenderItemUpdate) {
|
||||
auto itemID = getRenderItemID();
|
||||
|
@ -224,6 +233,9 @@ QVariant Base3DOverlay::getProperty(const QString& property) {
|
|||
if (property == "parentJointIndex") {
|
||||
return getParentJointIndex();
|
||||
}
|
||||
if (property == "isAA") {
|
||||
return _isAA;
|
||||
}
|
||||
|
||||
return Overlay::getProperty(property);
|
||||
}
|
||||
|
|
|
@ -36,11 +36,14 @@ public:
|
|||
bool getIgnoreRayIntersection() const { return _ignoreRayIntersection; }
|
||||
bool getDrawInFront() const { return _drawInFront; }
|
||||
|
||||
virtual bool isAA() const { return _isAA; }
|
||||
|
||||
void setLineWidth(float lineWidth) { _lineWidth = lineWidth; }
|
||||
void setIsSolid(bool isSolid) { _isSolid = isSolid; }
|
||||
void setIsDashedLine(bool isDashedLine) { _isDashedLine = isDashedLine; }
|
||||
void setIgnoreRayIntersection(bool value) { _ignoreRayIntersection = value; }
|
||||
void setDrawInFront(bool value) { _drawInFront = value; }
|
||||
void setIsAA(bool value) { _isAA = value; }
|
||||
|
||||
virtual AABox getBounds() const override = 0;
|
||||
|
||||
|
@ -64,6 +67,7 @@ protected:
|
|||
bool _isDashedLine;
|
||||
bool _ignoreRayIntersection;
|
||||
bool _drawInFront;
|
||||
bool _isAA;
|
||||
};
|
||||
|
||||
#endif // hifi_Base3DOverlay_h
|
||||
|
|
|
@ -38,6 +38,9 @@ namespace render {
|
|||
if (std::static_pointer_cast<Base3DOverlay>(overlay)->getDrawInFront()) {
|
||||
builder.withLayered();
|
||||
}
|
||||
if (!std::static_pointer_cast<Base3DOverlay>(overlay)->isAA()) {
|
||||
builder.withLayered();
|
||||
}
|
||||
if (overlay->getAlphaPulse() != 0.0f || overlay->getAlpha() != 1.0f) {
|
||||
builder.withTransparent();
|
||||
}
|
||||
|
@ -51,11 +54,17 @@ namespace render {
|
|||
}
|
||||
template <> int payloadGetLayer(const Overlay::Pointer& overlay) {
|
||||
// MAgic number while we are defining the layering mechanism:
|
||||
const int LAYER_2D = 2;
|
||||
const int LAYER_NO_AA = 3;
|
||||
const int LAYER_2D = 2;
|
||||
const int LAYER_3D_FRONT = 1;
|
||||
const int LAYER_3D = 0;
|
||||
|
||||
if (overlay->is3D()) {
|
||||
return (std::dynamic_pointer_cast<Base3DOverlay>(overlay)->getDrawInFront() ? LAYER_3D_FRONT : LAYER_3D);
|
||||
auto overlay3D = std::dynamic_pointer_cast<Base3DOverlay>(overlay);
|
||||
if (overlay3D->isAA())
|
||||
return (overlay3D->getDrawInFront() ? LAYER_3D_FRONT : LAYER_3D);
|
||||
else
|
||||
return LAYER_NO_AA;
|
||||
} else {
|
||||
return LAYER_2D;
|
||||
}
|
||||
|
|
|
@ -18,15 +18,17 @@
|
|||
#include <QtQuick/QQuickItem>
|
||||
#include <QtQml/QQmlContext>
|
||||
|
||||
#include <AbstractViewStateInterface.h>
|
||||
#include <gpu/Batch.h>
|
||||
#include <DependencyManager.h>
|
||||
#include <GeometryCache.h>
|
||||
#include <GeometryUtil.h>
|
||||
#include <TextureCache.h>
|
||||
#include <gl/OffscreenQmlSurface.h>
|
||||
#include <PathUtils.h>
|
||||
#include <gpu/Batch.h>
|
||||
#include <RegisteredMetaTypes.h>
|
||||
#include <TabletScriptingInterface.h>
|
||||
#include <TextureCache.h>
|
||||
#include <AbstractViewStateInterface.h>
|
||||
|
||||
#include <gl/OffscreenQmlSurface.h>
|
||||
#include <gl/OffscreenQmlSurfaceCache.h>
|
||||
|
||||
|
@ -37,7 +39,7 @@ static const float OPAQUE_ALPHA_THRESHOLD = 0.99f;
|
|||
|
||||
const QString Web3DOverlay::TYPE = "web3d";
|
||||
const QString Web3DOverlay::QML = "Web3DOverlay.qml";
|
||||
Web3DOverlay::Web3DOverlay() : _dpi(DPI) {
|
||||
Web3DOverlay::Web3DOverlay() : _dpi(DPI) {
|
||||
_touchDevice.setCapabilities(QTouchDevice::Position);
|
||||
_touchDevice.setType(QTouchDevice::TouchScreen);
|
||||
_touchDevice.setName("RenderableWebEntityItemTouchDevice");
|
||||
|
@ -51,13 +53,31 @@ Web3DOverlay::Web3DOverlay(const Web3DOverlay* Web3DOverlay) :
|
|||
_url(Web3DOverlay->_url),
|
||||
_scriptURL(Web3DOverlay->_scriptURL),
|
||||
_dpi(Web3DOverlay->_dpi),
|
||||
_resolution(Web3DOverlay->_resolution)
|
||||
_resolution(Web3DOverlay->_resolution),
|
||||
_showKeyboardFocusHighlight(Web3DOverlay->_showKeyboardFocusHighlight)
|
||||
{
|
||||
_geometryId = DependencyManager::get<GeometryCache>()->allocateID();
|
||||
}
|
||||
|
||||
Web3DOverlay::~Web3DOverlay() {
|
||||
if (_webSurface) {
|
||||
QQuickItem* rootItem = _webSurface->getRootItem();
|
||||
|
||||
if (rootItem && rootItem->objectName() == "tabletRoot") {
|
||||
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
|
||||
tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", nullptr, nullptr);
|
||||
}
|
||||
|
||||
// Fix for crash in QtWebEngineCore when rapidly switching domains
|
||||
// Call stop on the QWebEngineView before destroying OffscreenQMLSurface.
|
||||
if (rootItem) {
|
||||
QObject* obj = rootItem->findChild<QObject*>("webEngineView");
|
||||
if (obj) {
|
||||
// stop loading
|
||||
QMetaObject::invokeMethod(obj, "stop");
|
||||
}
|
||||
}
|
||||
|
||||
_webSurface->pause();
|
||||
_webSurface->disconnect(_connection);
|
||||
|
||||
|
@ -92,15 +112,54 @@ Web3DOverlay::~Web3DOverlay() {
|
|||
}
|
||||
|
||||
void Web3DOverlay::update(float deltatime) {
|
||||
// FIXME: applyTransformTo causes tablet overlay to detach from tablet entity.
|
||||
// Perhaps rather than deleting the following code it should be run only if isFacingAvatar() is true?
|
||||
/*
|
||||
if (usecTimestampNow() > _transformExpiry) {
|
||||
Transform transform = getTransform();
|
||||
applyTransformTo(transform);
|
||||
setTransform(transform);
|
||||
if (_webSurface) {
|
||||
// update globalPosition
|
||||
_webSurface->getRootContext()->setContextProperty("globalPosition", vec3toVariant(getPosition()));
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
QString Web3DOverlay::pickURL() {
|
||||
QUrl sourceUrl(_url);
|
||||
if (sourceUrl.scheme() == "http" || sourceUrl.scheme() == "https" ||
|
||||
_url.toLower().endsWith(".htm") || _url.toLower().endsWith(".html")) {
|
||||
|
||||
_webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/"));
|
||||
return "Web3DOverlay.qml";
|
||||
} else {
|
||||
return QUrl::fromLocalFile(PathUtils::resourcesPath()).toString() + "/" + _url;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Web3DOverlay::loadSourceURL() {
|
||||
|
||||
QUrl sourceUrl(_url);
|
||||
if (sourceUrl.scheme() == "http" || sourceUrl.scheme() == "https" ||
|
||||
_url.toLower().endsWith(".htm") || _url.toLower().endsWith(".html")) {
|
||||
|
||||
_webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/"));
|
||||
_webSurface->load("Web3DOverlay.qml");
|
||||
_webSurface->resume();
|
||||
_webSurface->getRootItem()->setProperty("url", _url);
|
||||
_webSurface->getRootItem()->setProperty("scriptURL", _scriptURL);
|
||||
_webSurface->getRootContext()->setContextProperty("ApplicationInterface", qApp);
|
||||
|
||||
} else {
|
||||
_webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath()));
|
||||
_webSurface->load(_url, [&](QQmlContext* context, QObject* obj) {});
|
||||
_webSurface->resume();
|
||||
|
||||
if (_webSurface->getRootItem() && _webSurface->getRootItem()->objectName() == "tabletRoot") {
|
||||
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
|
||||
auto flags = tabletScriptingInterface->getFlags();
|
||||
_webSurface->getRootContext()->setContextProperty("offscreenFlags", flags);
|
||||
tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", _webSurface->getRootItem(), _webSurface.data());
|
||||
|
||||
// Override min fps for tablet UI, for silky smooth scrolling
|
||||
_webSurface->setMaxFps(90);
|
||||
}
|
||||
}
|
||||
_webSurface->getRootContext()->setContextProperty("globalPosition", vec3toVariant(getPosition()));
|
||||
}
|
||||
|
||||
void Web3DOverlay::render(RenderArgs* args) {
|
||||
|
@ -111,10 +170,11 @@ void Web3DOverlay::render(RenderArgs* args) {
|
|||
QOpenGLContext * currentContext = QOpenGLContext::currentContext();
|
||||
QSurface * currentSurface = currentContext->surface();
|
||||
if (!_webSurface) {
|
||||
_webSurface = DependencyManager::get<OffscreenQmlSurfaceCache>()->acquire(QML);
|
||||
_webSurface = DependencyManager::get<OffscreenQmlSurfaceCache>()->acquire(pickURL());
|
||||
_webSurface->setMaxFps(10);
|
||||
// FIXME, the max FPS could be better managed by being dynamic (based on the number of current surfaces
|
||||
// and the current rendering load)
|
||||
loadSourceURL();
|
||||
_webSurface->resume();
|
||||
_webSurface->resize(QSize(_resolution.x, _resolution.y));
|
||||
_webSurface->getRootItem()->setProperty("url", _url);
|
||||
|
@ -143,7 +203,7 @@ void Web3DOverlay::render(RenderArgs* args) {
|
|||
point.setPos(windowPoint);
|
||||
QList<QTouchEvent::TouchPoint> touchPoints;
|
||||
touchPoints.push_back(point);
|
||||
QTouchEvent* touchEvent = new QTouchEvent(QEvent::TouchEnd, nullptr, Qt::NoModifier, Qt::TouchPointReleased,
|
||||
QTouchEvent* touchEvent = new QTouchEvent(QEvent::TouchEnd, nullptr, Qt::NoModifier, Qt::TouchPointReleased,
|
||||
touchPoints);
|
||||
touchEvent->setWindow(_webSurface->getWindow());
|
||||
touchEvent->setDevice(&_touchDevice);
|
||||
|
@ -160,7 +220,7 @@ void Web3DOverlay::render(RenderArgs* args) {
|
|||
vec4 color(toGlm(getColor()), getAlpha());
|
||||
|
||||
Transform transform = getTransform();
|
||||
|
||||
|
||||
// FIXME: applyTransformTo causes tablet overlay to detach from tablet entity.
|
||||
// Perhaps rather than deleting the following code it should be run only if isFacingAvatar() is true?
|
||||
/*
|
||||
|
@ -189,9 +249,9 @@ void Web3DOverlay::render(RenderArgs* args) {
|
|||
batch.setModelTransform(transform);
|
||||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
||||
if (color.a < OPAQUE_ALPHA_THRESHOLD) {
|
||||
geometryCache->bindTransparentWebBrowserProgram(batch);
|
||||
geometryCache->bindTransparentWebBrowserProgram(batch, _isAA);
|
||||
} else {
|
||||
geometryCache->bindOpaqueWebBrowserProgram(batch);
|
||||
geometryCache->bindOpaqueWebBrowserProgram(batch, _isAA);
|
||||
}
|
||||
geometryCache->renderQuad(batch, halfSize * -1.0f, halfSize, vec2(0), vec2(1), color, _geometryId);
|
||||
batch.setResourceTexture(0, args->_whiteTexture); // restore default white color after me
|
||||
|
@ -230,7 +290,7 @@ void Web3DOverlay::handlePointerEvent(const PointerEvent& event) {
|
|||
|
||||
if (event.getType() == PointerEvent::Move) {
|
||||
// Forward a mouse move event to the Web surface.
|
||||
QMouseEvent* mouseEvent = new QMouseEvent(QEvent::MouseMove, windowPoint, windowPoint, windowPoint, Qt::NoButton,
|
||||
QMouseEvent* mouseEvent = new QMouseEvent(QEvent::MouseMove, windowPoint, windowPoint, windowPoint, Qt::NoButton,
|
||||
Qt::NoButton, Qt::NoModifier);
|
||||
QCoreApplication::postEvent(_webSurface->getWindow(), mouseEvent);
|
||||
}
|
||||
|
@ -309,6 +369,11 @@ void Web3DOverlay::setProperties(const QVariantMap& properties) {
|
|||
if (dpi.isValid()) {
|
||||
_dpi = dpi.toFloat();
|
||||
}
|
||||
|
||||
auto showKeyboardFocusHighlight = properties["showKeyboardFocusHighlight"];
|
||||
if (showKeyboardFocusHighlight.isValid()) {
|
||||
_showKeyboardFocusHighlight = showKeyboardFocusHighlight.toBool();
|
||||
}
|
||||
}
|
||||
|
||||
QVariant Web3DOverlay::getProperty(const QString& property) {
|
||||
|
@ -324,6 +389,9 @@ QVariant Web3DOverlay::getProperty(const QString& property) {
|
|||
if (property == "dpi") {
|
||||
return _dpi;
|
||||
}
|
||||
if (property == "showKeyboardFocusHighlight") {
|
||||
return _showKeyboardFocusHighlight;
|
||||
}
|
||||
return Billboard3DOverlay::getProperty(property);
|
||||
}
|
||||
|
||||
|
@ -331,7 +399,7 @@ void Web3DOverlay::setURL(const QString& url) {
|
|||
_url = url;
|
||||
if (_webSurface) {
|
||||
AbstractViewStateInterface::instance()->postLambdaEvent([this, url] {
|
||||
_webSurface->getRootItem()->setProperty("url", url);
|
||||
loadSourceURL();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,6 +29,8 @@ public:
|
|||
Web3DOverlay(const Web3DOverlay* Web3DOverlay);
|
||||
virtual ~Web3DOverlay();
|
||||
|
||||
QString pickURL();
|
||||
void loadSourceURL();
|
||||
virtual void render(RenderArgs* args) override;
|
||||
virtual const render::ShapeKey getShapeKey() override;
|
||||
|
||||
|
@ -47,7 +49,7 @@ public:
|
|||
|
||||
glm::vec2 getSize();
|
||||
|
||||
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
||||
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal) override;
|
||||
|
||||
virtual Web3DOverlay* createClone() const override;
|
||||
|
@ -68,6 +70,7 @@ private:
|
|||
float _dpi;
|
||||
vec2 _resolution{ 640, 480 };
|
||||
int _geometryId { 0 };
|
||||
bool _showKeyboardFocusHighlight{ true };
|
||||
|
||||
bool _pressed{ false };
|
||||
QTouchDevice _touchDevice;
|
||||
|
|
|
@ -112,6 +112,42 @@ private:
|
|||
bool _quit { false };
|
||||
};
|
||||
|
||||
void AudioInjectorsThread::prepare() {
|
||||
_audio->prepareLocalAudioInjectors();
|
||||
}
|
||||
|
||||
static void channelUpmix(int16_t* source, int16_t* dest, int numSamples, int numExtraChannels) {
|
||||
for (int i = 0; i < numSamples/2; i++) {
|
||||
|
||||
// read 2 samples
|
||||
int16_t left = *source++;
|
||||
int16_t right = *source++;
|
||||
|
||||
// write 2 + N samples
|
||||
*dest++ = left;
|
||||
*dest++ = right;
|
||||
for (int n = 0; n < numExtraChannels; n++) {
|
||||
*dest++ = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void channelDownmix(int16_t* source, int16_t* dest, int numSamples) {
|
||||
for (int i = 0; i < numSamples/2; i++) {
|
||||
|
||||
// read 2 samples
|
||||
int16_t left = *source++;
|
||||
int16_t right = *source++;
|
||||
|
||||
// write 1 sample
|
||||
*dest++ = (int16_t)((left + right) / 2);
|
||||
}
|
||||
}
|
||||
|
||||
static inline float convertToFloat(int16_t sample) {
|
||||
return (float)sample * (1 / 32768.0f);
|
||||
}
|
||||
|
||||
AudioClient::AudioClient() :
|
||||
AbstractAudioInterface(),
|
||||
_gate(this),
|
||||
|
@ -127,6 +163,7 @@ AudioClient::AudioClient() :
|
|||
_loopbackAudioOutput(NULL),
|
||||
_loopbackOutputDevice(NULL),
|
||||
_inputRingBuffer(0),
|
||||
_localInjectorsStream(0),
|
||||
_receivedAudioStream(RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES),
|
||||
_isStereoInput(false),
|
||||
_outputStarveDetectionStartTimeMsec(0),
|
||||
|
@ -144,13 +181,18 @@ AudioClient::AudioClient() :
|
|||
_reverbOptions(&_scriptReverbOptions),
|
||||
_inputToNetworkResampler(NULL),
|
||||
_networkToOutputResampler(NULL),
|
||||
_localToOutputResampler(NULL),
|
||||
_localAudioThread(this),
|
||||
_audioLimiter(AudioConstants::SAMPLE_RATE, OUTPUT_CHANNEL_COUNT),
|
||||
_outgoingAvatarAudioSequenceNumber(0),
|
||||
_audioOutputIODevice(_receivedAudioStream, this),
|
||||
_audioOutputIODevice(_localInjectorsStream, _receivedAudioStream, this),
|
||||
_stats(&_receivedAudioStream),
|
||||
_inputGate(),
|
||||
_positionGetter(DEFAULT_POSITION_GETTER),
|
||||
_orientationGetter(DEFAULT_ORIENTATION_GETTER) {
|
||||
// avoid putting a lock in the device callback
|
||||
assert(_localSamplesAvailable.is_lock_free());
|
||||
|
||||
// deprecate legacy settings
|
||||
{
|
||||
Setting::Handle<int>::Deprecated("maxFramesOverDesired", InboundAudioStream::MAX_FRAMES_OVER_DESIRED);
|
||||
|
@ -176,6 +218,10 @@ AudioClient::AudioClient() :
|
|||
_checkDevicesThread->setPriority(QThread::LowPriority);
|
||||
_checkDevicesThread->start();
|
||||
|
||||
// start a thread to process local injectors
|
||||
_localAudioThread.setObjectName("LocalAudio Thread");
|
||||
_localAudioThread.start();
|
||||
|
||||
configureReverb();
|
||||
|
||||
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
|
||||
|
@ -213,6 +259,7 @@ void AudioClient::reset() {
|
|||
_stats.reset();
|
||||
_sourceReverb.reset();
|
||||
_listenerReverb.reset();
|
||||
_localReverb.reset();
|
||||
}
|
||||
|
||||
void AudioClient::audioMixerKilled() {
|
||||
|
@ -365,7 +412,7 @@ QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) {
|
|||
CoUninitialize();
|
||||
}
|
||||
|
||||
qCDebug(audioclient) << "DEBUG [" << deviceName << "] [" << getNamedAudioDeviceForMode(mode, deviceName).deviceName() << "]";
|
||||
qCDebug(audioclient) << "[" << deviceName << "] [" << getNamedAudioDeviceForMode(mode, deviceName).deviceName() << "]";
|
||||
|
||||
return getNamedAudioDeviceForMode(mode, deviceName);
|
||||
#endif
|
||||
|
@ -387,12 +434,12 @@ bool nativeFormatForAudioDevice(const QAudioDeviceInfo& audioDevice,
|
|||
audioFormat.setByteOrder(QAudioFormat::LittleEndian);
|
||||
|
||||
if (!audioDevice.isFormatSupported(audioFormat)) {
|
||||
qCDebug(audioclient) << "WARNING: The native format is" << audioFormat << "but isFormatSupported() failed.";
|
||||
qCWarning(audioclient) << "The native format is" << audioFormat << "but isFormatSupported() failed.";
|
||||
return false;
|
||||
}
|
||||
// converting to/from this rate must produce an integral number of samples
|
||||
if (audioFormat.sampleRate() * AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL % AudioConstants::SAMPLE_RATE != 0) {
|
||||
qCDebug(audioclient) << "WARNING: The native sample rate [" << audioFormat.sampleRate() << "] is not supported.";
|
||||
qCWarning(audioclient) << "The native sample rate [" << audioFormat.sampleRate() << "] is not supported.";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
@ -726,12 +773,12 @@ QVector<QString> AudioClient::getDeviceNames(QAudio::Mode mode) {
|
|||
}
|
||||
|
||||
bool AudioClient::switchInputToAudioDevice(const QString& inputDeviceName) {
|
||||
qCDebug(audioclient) << "DEBUG [" << inputDeviceName << "] [" << getNamedAudioDeviceForMode(QAudio::AudioInput, inputDeviceName).deviceName() << "]";
|
||||
qCDebug(audioclient) << "[" << inputDeviceName << "] [" << getNamedAudioDeviceForMode(QAudio::AudioInput, inputDeviceName).deviceName() << "]";
|
||||
return switchInputToAudioDevice(getNamedAudioDeviceForMode(QAudio::AudioInput, inputDeviceName));
|
||||
}
|
||||
|
||||
bool AudioClient::switchOutputToAudioDevice(const QString& outputDeviceName) {
|
||||
qCDebug(audioclient) << "DEBUG [" << outputDeviceName << "] [" << getNamedAudioDeviceForMode(QAudio::AudioOutput, outputDeviceName).deviceName() << "]";
|
||||
qCDebug(audioclient) << "[" << outputDeviceName << "] [" << getNamedAudioDeviceForMode(QAudio::AudioOutput, outputDeviceName).deviceName() << "]";
|
||||
return switchOutputToAudioDevice(getNamedAudioDeviceForMode(QAudio::AudioOutput, outputDeviceName));
|
||||
}
|
||||
|
||||
|
@ -762,6 +809,7 @@ void AudioClient::configureReverb() {
|
|||
p.wetDryMix = _reverbOptions->getWetDryMix();
|
||||
|
||||
_listenerReverb.setParameters(&p);
|
||||
_localReverb.setParameters(&p);
|
||||
|
||||
// used only for adding self-reverb to loopback audio
|
||||
p.sampleRate = _outputFormat.sampleRate();
|
||||
|
@ -808,6 +856,7 @@ void AudioClient::setReverb(bool reverb) {
|
|||
if (!_reverb) {
|
||||
_sourceReverb.reset();
|
||||
_listenerReverb.reset();
|
||||
_localReverb.reset();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -841,36 +890,6 @@ void AudioClient::setReverbOptions(const AudioEffectOptions* options) {
|
|||
}
|
||||
}
|
||||
|
||||
static void channelUpmix(int16_t* source, int16_t* dest, int numSamples, int numExtraChannels) {
|
||||
|
||||
for (int i = 0; i < numSamples/2; i++) {
|
||||
|
||||
// read 2 samples
|
||||
int16_t left = *source++;
|
||||
int16_t right = *source++;
|
||||
|
||||
// write 2 + N samples
|
||||
*dest++ = left;
|
||||
*dest++ = right;
|
||||
for (int n = 0; n < numExtraChannels; n++) {
|
||||
*dest++ = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void channelDownmix(int16_t* source, int16_t* dest, int numSamples) {
|
||||
|
||||
for (int i = 0; i < numSamples/2; i++) {
|
||||
|
||||
// read 2 samples
|
||||
int16_t left = *source++;
|
||||
int16_t right = *source++;
|
||||
|
||||
// write 1 sample
|
||||
*dest++ = (int16_t)((left + right) / 2);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioClient::handleLocalEchoAndReverb(QByteArray& inputByteArray) {
|
||||
// If there is server echo, reverb will be applied to the recieved audio stream so no need to have it here.
|
||||
bool hasReverb = _reverb || _receivedAudioStream.hasReverb();
|
||||
|
@ -1082,14 +1101,78 @@ void AudioClient::handleRecordedAudioInput(const QByteArray& audio) {
|
|||
PacketType::MicrophoneAudioWithEcho, _selectedCodecName);
|
||||
}
|
||||
|
||||
void AudioClient::mixLocalAudioInjectors(float* mixBuffer) {
|
||||
void AudioClient::prepareLocalAudioInjectors() {
|
||||
if (_outputPeriod == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int bufferCapacity = _localInjectorsStream.getSampleCapacity();
|
||||
if (_localToOutputResampler) {
|
||||
// avoid overwriting the buffer,
|
||||
// instead of failing on writes because the buffer is used as a lock-free pipe
|
||||
bufferCapacity -=
|
||||
_localToOutputResampler->getMaxOutput(AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL) *
|
||||
AudioConstants::STEREO;
|
||||
bufferCapacity += 1;
|
||||
}
|
||||
|
||||
int samplesNeeded = std::numeric_limits<int>::max();
|
||||
while (samplesNeeded > 0) {
|
||||
// lock for every write to avoid locking out the device callback
|
||||
// this lock is intentional - the buffer is only lock-free in its use in the device callback
|
||||
Lock lock(_localAudioMutex);
|
||||
|
||||
samplesNeeded = bufferCapacity - _localSamplesAvailable.load(std::memory_order_relaxed);
|
||||
if (samplesNeeded <= 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
// get a network frame of local injectors' audio
|
||||
if (!mixLocalAudioInjectors(_localMixBuffer)) {
|
||||
break;
|
||||
}
|
||||
|
||||
// reverb
|
||||
if (_reverb) {
|
||||
_localReverb.render(_localMixBuffer, _localMixBuffer, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL);
|
||||
}
|
||||
|
||||
int samples;
|
||||
if (_localToOutputResampler) {
|
||||
// resample to output sample rate
|
||||
int frames = _localToOutputResampler->render(_localMixBuffer, _localOutputMixBuffer,
|
||||
AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL);
|
||||
|
||||
// write to local injectors' ring buffer
|
||||
samples = frames * AudioConstants::STEREO;
|
||||
_localInjectorsStream.writeSamples(_localOutputMixBuffer, samples);
|
||||
|
||||
} else {
|
||||
// write to local injectors' ring buffer
|
||||
samples = AudioConstants::NETWORK_FRAME_SAMPLES_STEREO;
|
||||
_localInjectorsStream.writeSamples(_localMixBuffer,
|
||||
AudioConstants::NETWORK_FRAME_SAMPLES_STEREO);
|
||||
}
|
||||
|
||||
_localSamplesAvailable.fetch_add(samples, std::memory_order_release);
|
||||
samplesNeeded -= samples;
|
||||
}
|
||||
}
|
||||
|
||||
bool AudioClient::mixLocalAudioInjectors(float* mixBuffer) {
|
||||
|
||||
QVector<AudioInjector*> injectorsToRemove;
|
||||
|
||||
// lock the injector vector
|
||||
Lock lock(_injectorsMutex);
|
||||
|
||||
for (AudioInjector* injector : getActiveLocalAudioInjectors()) {
|
||||
if (_activeLocalAudioInjectors.size() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(mixBuffer, 0, AudioConstants::NETWORK_FRAME_SAMPLES_STEREO * sizeof(float));
|
||||
|
||||
for (AudioInjector* injector : _activeLocalAudioInjectors) {
|
||||
if (injector->getLocalBuffer()) {
|
||||
|
||||
static const int HRTF_DATASET_INDEX = 1;
|
||||
|
@ -1098,8 +1181,8 @@ void AudioClient::mixLocalAudioInjectors(float* mixBuffer) {
|
|||
qint64 bytesToRead = numChannels * AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL;
|
||||
|
||||
// get one frame from the injector
|
||||
memset(_scratchBuffer, 0, bytesToRead);
|
||||
if (0 < injector->getLocalBuffer()->readData((char*)_scratchBuffer, bytesToRead)) {
|
||||
memset(_localScratchBuffer, 0, bytesToRead);
|
||||
if (0 < injector->getLocalBuffer()->readData((char*)_localScratchBuffer, bytesToRead)) {
|
||||
|
||||
if (injector->isAmbisonic()) {
|
||||
|
||||
|
@ -1119,7 +1202,7 @@ void AudioClient::mixLocalAudioInjectors(float* mixBuffer) {
|
|||
float qz = relativeOrientation.y;
|
||||
|
||||
// Ambisonic gets spatialized into mixBuffer
|
||||
injector->getLocalFOA().render(_scratchBuffer, mixBuffer, HRTF_DATASET_INDEX,
|
||||
injector->getLocalFOA().render(_localScratchBuffer, mixBuffer, HRTF_DATASET_INDEX,
|
||||
qw, qx, qy, qz, gain, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL);
|
||||
|
||||
} else if (injector->isStereo()) {
|
||||
|
@ -1127,7 +1210,7 @@ void AudioClient::mixLocalAudioInjectors(float* mixBuffer) {
|
|||
// stereo gets directly mixed into mixBuffer
|
||||
float gain = injector->getVolume();
|
||||
for (int i = 0; i < AudioConstants::NETWORK_FRAME_SAMPLES_STEREO; i++) {
|
||||
mixBuffer[i] += (float)_scratchBuffer[i] * (1/32768.0f) * gain;
|
||||
mixBuffer[i] += convertToFloat(_localScratchBuffer[i]) * gain;
|
||||
}
|
||||
|
||||
} else {
|
||||
|
@ -1136,10 +1219,10 @@ void AudioClient::mixLocalAudioInjectors(float* mixBuffer) {
|
|||
glm::vec3 relativePosition = injector->getPosition() - _positionGetter();
|
||||
float distance = glm::max(glm::length(relativePosition), EPSILON);
|
||||
float gain = gainForSource(distance, injector->getVolume());
|
||||
float azimuth = azimuthForSource(relativePosition);
|
||||
float azimuth = azimuthForSource(relativePosition);
|
||||
|
||||
// mono gets spatialized into mixBuffer
|
||||
injector->getLocalHRTF().render(_scratchBuffer, mixBuffer, HRTF_DATASET_INDEX,
|
||||
injector->getLocalHRTF().render(_localScratchBuffer, mixBuffer, HRTF_DATASET_INDEX,
|
||||
azimuth, distance, gain, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL);
|
||||
}
|
||||
|
||||
|
@ -1160,8 +1243,10 @@ void AudioClient::mixLocalAudioInjectors(float* mixBuffer) {
|
|||
|
||||
for (AudioInjector* injector : injectorsToRemove) {
|
||||
qCDebug(audioclient) << "removing injector";
|
||||
getActiveLocalAudioInjectors().removeOne(injector);
|
||||
_activeLocalAudioInjectors.removeOne(injector);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AudioClient::processReceivedSamples(const QByteArray& decodedBuffer, QByteArray& outputBuffer) {
|
||||
|
@ -1172,33 +1257,24 @@ void AudioClient::processReceivedSamples(const QByteArray& decodedBuffer, QByteA
|
|||
outputBuffer.resize(_outputFrameSize * AudioConstants::SAMPLE_SIZE);
|
||||
int16_t* outputSamples = reinterpret_cast<int16_t*>(outputBuffer.data());
|
||||
|
||||
// convert network audio to float
|
||||
for (int i = 0; i < AudioConstants::NETWORK_FRAME_SAMPLES_STEREO; i++) {
|
||||
_mixBuffer[i] = (float)decodedSamples[i] * (1/32768.0f);
|
||||
}
|
||||
|
||||
// mix in active injectors
|
||||
if (getActiveLocalAudioInjectors().size() > 0) {
|
||||
mixLocalAudioInjectors(_mixBuffer);
|
||||
}
|
||||
bool hasReverb = _reverb || _receivedAudioStream.hasReverb();
|
||||
|
||||
// apply stereo reverb
|
||||
bool hasReverb = _reverb || _receivedAudioStream.hasReverb();
|
||||
if (hasReverb) {
|
||||
updateReverbOptions();
|
||||
_listenerReverb.render(_mixBuffer, _mixBuffer, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL);
|
||||
int16_t* reverbSamples = _networkToOutputResampler ? _networkScratchBuffer : outputSamples;
|
||||
_listenerReverb.render(decodedSamples, reverbSamples, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL);
|
||||
}
|
||||
|
||||
// resample to output sample rate
|
||||
if (_networkToOutputResampler) {
|
||||
const int16_t* inputSamples = hasReverb ? _networkScratchBuffer : decodedSamples;
|
||||
_networkToOutputResampler->render(inputSamples, outputSamples, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL);
|
||||
}
|
||||
|
||||
// resample to output sample rate
|
||||
_audioLimiter.render(_mixBuffer, _scratchBuffer, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL);
|
||||
_networkToOutputResampler->render(_scratchBuffer, outputSamples, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL);
|
||||
|
||||
} else {
|
||||
|
||||
// no resampling needed
|
||||
_audioLimiter.render(_mixBuffer, outputSamples, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL);
|
||||
// if no transformations were applied, we still need to copy the buffer
|
||||
if (!hasReverb && !_networkToOutputResampler) {
|
||||
memcpy(outputSamples, decodedSamples, decodedBuffer.size());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1381,6 +1457,9 @@ void AudioClient::outputNotify() {
|
|||
bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDeviceInfo) {
|
||||
bool supportedFormat = false;
|
||||
|
||||
Lock lock(_localAudioMutex);
|
||||
_localSamplesAvailable.exchange(0, std::memory_order_release);
|
||||
|
||||
// cleanup any previously initialized device
|
||||
if (_audioOutput) {
|
||||
_audioOutput->stop();
|
||||
|
@ -1391,12 +1470,24 @@ bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDevice
|
|||
_loopbackOutputDevice = NULL;
|
||||
delete _loopbackAudioOutput;
|
||||
_loopbackAudioOutput = NULL;
|
||||
|
||||
delete[] _outputMixBuffer;
|
||||
_outputMixBuffer = NULL;
|
||||
|
||||
delete[] _outputScratchBuffer;
|
||||
_outputScratchBuffer = NULL;
|
||||
|
||||
delete[] _localOutputMixBuffer;
|
||||
_localOutputMixBuffer = NULL;
|
||||
}
|
||||
|
||||
if (_networkToOutputResampler) {
|
||||
// if we were using an input to network resampler, delete it here
|
||||
delete _networkToOutputResampler;
|
||||
_networkToOutputResampler = NULL;
|
||||
|
||||
delete _localToOutputResampler;
|
||||
_localToOutputResampler = NULL;
|
||||
}
|
||||
|
||||
if (!outputDeviceInfo.isNull()) {
|
||||
|
@ -1416,6 +1507,7 @@ bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDevice
|
|||
assert(_outputFormat.sampleSize() == 16);
|
||||
|
||||
_networkToOutputResampler = new AudioSRC(_desiredOutputFormat.sampleRate(), _outputFormat.sampleRate(), OUTPUT_CHANNEL_COUNT);
|
||||
_localToOutputResampler = new AudioSRC(_desiredOutputFormat.sampleRate(), _outputFormat.sampleRate(), OUTPUT_CHANNEL_COUNT);
|
||||
|
||||
} else {
|
||||
qCDebug(audioclient) << "No resampling required for network output to match actual output format.";
|
||||
|
@ -1441,6 +1533,14 @@ bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDevice
|
|||
_audioOutput->start(&_audioOutputIODevice);
|
||||
lock.unlock();
|
||||
|
||||
int periodSampleSize = _audioOutput->periodSize() / AudioConstants::SAMPLE_SIZE;
|
||||
// device callback is not restricted to periodSampleSize, so double the mix/scratch buffer sizes
|
||||
_outputPeriod = periodSampleSize * 2;
|
||||
_outputMixBuffer = new float[_outputPeriod];
|
||||
_outputScratchBuffer = new int16_t[_outputPeriod];
|
||||
_localOutputMixBuffer = new float[_outputPeriod];
|
||||
_localInjectorsStream.resizeForFrameSize(_outputPeriod * 2);
|
||||
|
||||
qCDebug(audioclient) << "Output Buffer capacity in frames: " << _audioOutput->bufferSize() / AudioConstants::SAMPLE_SIZE / (float)deviceFrameSize <<
|
||||
"requested bytes:" << requestedSize << "actual bytes:" << _audioOutput->bufferSize() <<
|
||||
"os default:" << osDefaultBufferSize << "period size:" << _audioOutput->periodSize();
|
||||
|
@ -1550,26 +1650,61 @@ qint64 AudioClient::AudioOutputIODevice::readData(char * data, qint64 maxSize) {
|
|||
// samples requested from OUTPUT_CHANNEL_COUNT
|
||||
int deviceChannelCount = _audio->_outputFormat.channelCount();
|
||||
int samplesRequested = (int)(maxSize / AudioConstants::SAMPLE_SIZE) * OUTPUT_CHANNEL_COUNT / deviceChannelCount;
|
||||
// restrict samplesRequested to the size of our mix/scratch buffers
|
||||
samplesRequested = std::min(samplesRequested, _audio->_outputPeriod);
|
||||
|
||||
int samplesPopped;
|
||||
int bytesWritten;
|
||||
int16_t* scratchBuffer = _audio->_outputScratchBuffer;
|
||||
float* mixBuffer = _audio->_outputMixBuffer;
|
||||
|
||||
if ((samplesPopped = _receivedAudioStream.popSamples(samplesRequested, false)) > 0) {
|
||||
qCDebug(audiostream, "Read %d samples from buffer (%d available)", samplesPopped, _receivedAudioStream.getSamplesAvailable());
|
||||
int networkSamplesPopped;
|
||||
if ((networkSamplesPopped = _receivedAudioStream.popSamples(samplesRequested, false)) > 0) {
|
||||
qCDebug(audiostream, "Read %d samples from buffer (%d available, %d requested)", networkSamplesPopped, _receivedAudioStream.getSamplesAvailable(), samplesRequested);
|
||||
AudioRingBuffer::ConstIterator lastPopOutput = _receivedAudioStream.getLastPopOutput();
|
||||
lastPopOutput.readSamples(scratchBuffer, networkSamplesPopped);
|
||||
|
||||
// if required, upmix or downmix to deviceChannelCount
|
||||
if (deviceChannelCount == OUTPUT_CHANNEL_COUNT) {
|
||||
lastPopOutput.readSamples((int16_t*)data, samplesPopped);
|
||||
} else if (deviceChannelCount > OUTPUT_CHANNEL_COUNT) {
|
||||
lastPopOutput.readSamplesWithUpmix((int16_t*)data, samplesPopped, deviceChannelCount - OUTPUT_CHANNEL_COUNT);
|
||||
} else {
|
||||
lastPopOutput.readSamplesWithDownmix((int16_t*)data, samplesPopped);
|
||||
for (int i = 0; i < networkSamplesPopped; i++) {
|
||||
mixBuffer[i] = convertToFloat(scratchBuffer[i]);
|
||||
}
|
||||
bytesWritten = (samplesPopped * AudioConstants::SAMPLE_SIZE) * deviceChannelCount / OUTPUT_CHANNEL_COUNT;
|
||||
|
||||
samplesRequested = networkSamplesPopped;
|
||||
}
|
||||
|
||||
int injectorSamplesPopped = 0;
|
||||
{
|
||||
Lock lock(_audio->_localAudioMutex);
|
||||
bool append = networkSamplesPopped > 0;
|
||||
samplesRequested = std::min(samplesRequested, _audio->_localSamplesAvailable.load(std::memory_order_acquire));
|
||||
if ((injectorSamplesPopped = _localInjectorsStream.appendSamples(mixBuffer, samplesRequested, append)) > 0) {
|
||||
_audio->_localSamplesAvailable.fetch_sub(injectorSamplesPopped, std::memory_order_release);
|
||||
qCDebug(audiostream, "Read %d samples from injectors (%d available, %d requested)", injectorSamplesPopped, _localInjectorsStream.samplesAvailable(), samplesRequested);
|
||||
}
|
||||
}
|
||||
|
||||
// prepare injectors for the next callback
|
||||
QMetaObject::invokeMethod(&_audio->_localAudioThread, "prepare", Qt::QueuedConnection);
|
||||
|
||||
int samplesPopped = std::max(networkSamplesPopped, injectorSamplesPopped);
|
||||
int framesPopped = samplesPopped / AudioConstants::STEREO;
|
||||
int bytesWritten;
|
||||
if (samplesPopped > 0) {
|
||||
if (deviceChannelCount == OUTPUT_CHANNEL_COUNT) {
|
||||
// limit the audio
|
||||
_audio->_audioLimiter.render(mixBuffer, (int16_t*)data, framesPopped);
|
||||
} else {
|
||||
_audio->_audioLimiter.render(mixBuffer, scratchBuffer, framesPopped);
|
||||
|
||||
// upmix or downmix to deviceChannelCount
|
||||
if (deviceChannelCount > OUTPUT_CHANNEL_COUNT) {
|
||||
int extraChannels = deviceChannelCount - OUTPUT_CHANNEL_COUNT;
|
||||
channelUpmix(scratchBuffer, (int16_t*)data, samplesPopped, extraChannels);
|
||||
} else {
|
||||
channelDownmix(scratchBuffer, (int16_t*)data, samplesPopped);
|
||||
}
|
||||
}
|
||||
|
||||
bytesWritten = framesPopped * AudioConstants::SAMPLE_SIZE * deviceChannelCount;
|
||||
} else {
|
||||
// nothing on network, don't grab anything from injectors, and just return 0s
|
||||
// this will flood the log: qCDebug(audioclient, "empty/partial network buffer");
|
||||
memset(data, 0, maxSize);
|
||||
bytesWritten = maxSize;
|
||||
}
|
||||
|
|
|
@ -69,9 +69,24 @@ class QIODevice;
|
|||
class Transform;
|
||||
class NLPacket;
|
||||
|
||||
class AudioInjectorsThread : public QThread {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
AudioInjectorsThread(AudioClient* audio) : _audio(audio) {}
|
||||
|
||||
public slots :
|
||||
void prepare();
|
||||
|
||||
private:
|
||||
AudioClient* _audio;
|
||||
};
|
||||
|
||||
class AudioClient : public AbstractAudioInterface, public Dependency {
|
||||
Q_OBJECT
|
||||
SINGLETON_DEPENDENCY
|
||||
|
||||
using LocalInjectorsStream = AudioMixRingBuffer;
|
||||
public:
|
||||
static const int MIN_BUFFER_FRAMES;
|
||||
static const int MAX_BUFFER_FRAMES;
|
||||
|
@ -84,8 +99,10 @@ public:
|
|||
|
||||
class AudioOutputIODevice : public QIODevice {
|
||||
public:
|
||||
AudioOutputIODevice(MixedProcessedAudioStream& receivedAudioStream, AudioClient* audio) :
|
||||
_receivedAudioStream(receivedAudioStream), _audio(audio), _unfulfilledReads(0) {};
|
||||
AudioOutputIODevice(LocalInjectorsStream& localInjectorsStream, MixedProcessedAudioStream& receivedAudioStream,
|
||||
AudioClient* audio) :
|
||||
_localInjectorsStream(localInjectorsStream), _receivedAudioStream(receivedAudioStream),
|
||||
_audio(audio), _unfulfilledReads(0) {}
|
||||
|
||||
void start() { open(QIODevice::ReadOnly | QIODevice::Unbuffered); }
|
||||
void stop() { close(); }
|
||||
|
@ -93,6 +110,7 @@ public:
|
|||
qint64 writeData(const char * data, qint64 maxSize) override { return 0; }
|
||||
int getRecentUnfulfilledReads() { int unfulfilledReads = _unfulfilledReads; _unfulfilledReads = 0; return unfulfilledReads; }
|
||||
private:
|
||||
LocalInjectorsStream& _localInjectorsStream;
|
||||
MixedProcessedAudioStream& _receivedAudioStream;
|
||||
AudioClient* _audio;
|
||||
int _unfulfilledReads;
|
||||
|
@ -129,8 +147,6 @@ public:
|
|||
|
||||
Q_INVOKABLE void setAvatarBoundingBoxParameters(glm::vec3 corner, glm::vec3 scale);
|
||||
|
||||
QVector<AudioInjector*>& getActiveLocalAudioInjectors() { return _activeLocalAudioInjectors; }
|
||||
|
||||
void checkDevices();
|
||||
|
||||
static const float CALLBACK_ACCELERATOR_RATIO;
|
||||
|
@ -171,6 +187,7 @@ public slots:
|
|||
|
||||
int setOutputBufferSize(int numFrames, bool persist = true);
|
||||
|
||||
void prepareLocalAudioInjectors();
|
||||
bool outputLocalInjector(AudioInjector* injector) override;
|
||||
bool shouldLoopbackInjectors() override { return _shouldEchoToServer; }
|
||||
|
||||
|
@ -218,7 +235,7 @@ protected:
|
|||
|
||||
private:
|
||||
void outputFormatChanged();
|
||||
void mixLocalAudioInjectors(float* mixBuffer);
|
||||
bool mixLocalAudioInjectors(float* mixBuffer);
|
||||
float azimuthForSource(const glm::vec3& relativePosition);
|
||||
float gainForSource(float distance, float volume);
|
||||
|
||||
|
@ -262,6 +279,10 @@ private:
|
|||
QAudioOutput* _loopbackAudioOutput;
|
||||
QIODevice* _loopbackOutputDevice;
|
||||
AudioRingBuffer _inputRingBuffer;
|
||||
LocalInjectorsStream _localInjectorsStream;
|
||||
// In order to use _localInjectorsStream as a lock-free pipe,
|
||||
// use it with a single producer/consumer, and track available samples
|
||||
std::atomic<int> _localSamplesAvailable { 0 };
|
||||
MixedProcessedAudioStream _receivedAudioStream;
|
||||
bool _isStereoInput;
|
||||
|
||||
|
@ -292,14 +313,28 @@ private:
|
|||
AudioEffectOptions* _reverbOptions;
|
||||
AudioReverb _sourceReverb { AudioConstants::SAMPLE_RATE };
|
||||
AudioReverb _listenerReverb { AudioConstants::SAMPLE_RATE };
|
||||
AudioReverb _localReverb { AudioConstants::SAMPLE_RATE };
|
||||
|
||||
// possible streams needed for resample
|
||||
AudioSRC* _inputToNetworkResampler;
|
||||
AudioSRC* _networkToOutputResampler;
|
||||
AudioSRC* _localToOutputResampler;
|
||||
|
||||
// for network audio (used by network audio thread)
|
||||
int16_t _networkScratchBuffer[AudioConstants::NETWORK_FRAME_SAMPLES_AMBISONIC];
|
||||
|
||||
// for local audio (used by audio injectors thread)
|
||||
float _localMixBuffer[AudioConstants::NETWORK_FRAME_SAMPLES_STEREO];
|
||||
int16_t _localScratchBuffer[AudioConstants::NETWORK_FRAME_SAMPLES_AMBISONIC];
|
||||
float* _localOutputMixBuffer { NULL };
|
||||
AudioInjectorsThread _localAudioThread;
|
||||
Mutex _localAudioMutex;
|
||||
|
||||
// for output audio (used by this thread)
|
||||
int _outputPeriod { 0 };
|
||||
float* _outputMixBuffer { NULL };
|
||||
int16_t* _outputScratchBuffer { NULL };
|
||||
|
||||
// for local hrtf-ing
|
||||
float _mixBuffer[AudioConstants::NETWORK_FRAME_SAMPLES_STEREO];
|
||||
int16_t _scratchBuffer[AudioConstants::NETWORK_FRAME_SAMPLES_AMBISONIC];
|
||||
AudioLimiter _audioLimiter;
|
||||
|
||||
// Adds Reverb
|
||||
|
|
|
@ -26,46 +26,51 @@
|
|||
static const QString RING_BUFFER_OVERFLOW_DEBUG { "AudioRingBuffer::writeData has overflown the buffer. Overwriting old data." };
|
||||
static const QString DROPPED_SILENT_DEBUG { "AudioRingBuffer::addSilentSamples dropping silent samples to prevent overflow." };
|
||||
|
||||
AudioRingBuffer::AudioRingBuffer(int numFrameSamples, int numFramesCapacity) :
|
||||
template <class T>
|
||||
AudioRingBufferTemplate<T>::AudioRingBufferTemplate(int numFrameSamples, int numFramesCapacity) :
|
||||
_numFrameSamples(numFrameSamples),
|
||||
_frameCapacity(numFramesCapacity),
|
||||
_sampleCapacity(numFrameSamples * numFramesCapacity),
|
||||
_bufferLength(numFrameSamples * (numFramesCapacity + 1))
|
||||
{
|
||||
if (numFrameSamples) {
|
||||
_buffer = new int16_t[_bufferLength];
|
||||
memset(_buffer, 0, _bufferLength * sizeof(int16_t));
|
||||
_buffer = new Sample[_bufferLength];
|
||||
memset(_buffer, 0, _bufferLength * SampleSize);
|
||||
_nextOutput = _buffer;
|
||||
_endOfLastWrite = _buffer;
|
||||
}
|
||||
|
||||
static QString repeatedOverflowMessage = LogHandler::getInstance().addRepeatedMessageRegex(RING_BUFFER_OVERFLOW_DEBUG);
|
||||
static QString repeatedDroppedMessage = LogHandler::getInstance().addRepeatedMessageRegex(DROPPED_SILENT_DEBUG);
|
||||
};
|
||||
}
|
||||
|
||||
AudioRingBuffer::~AudioRingBuffer() {
|
||||
template <class T>
|
||||
AudioRingBufferTemplate<T>::~AudioRingBufferTemplate() {
|
||||
delete[] _buffer;
|
||||
}
|
||||
|
||||
void AudioRingBuffer::clear() {
|
||||
template <class T>
|
||||
void AudioRingBufferTemplate<T>::clear() {
|
||||
_endOfLastWrite = _buffer;
|
||||
_nextOutput = _buffer;
|
||||
}
|
||||
|
||||
void AudioRingBuffer::reset() {
|
||||
template <class T>
|
||||
void AudioRingBufferTemplate<T>::reset() {
|
||||
clear();
|
||||
_overflowCount = 0;
|
||||
}
|
||||
|
||||
void AudioRingBuffer::resizeForFrameSize(int numFrameSamples) {
|
||||
template <class T>
|
||||
void AudioRingBufferTemplate<T>::resizeForFrameSize(int numFrameSamples) {
|
||||
delete[] _buffer;
|
||||
_numFrameSamples = numFrameSamples;
|
||||
_sampleCapacity = numFrameSamples * _frameCapacity;
|
||||
_bufferLength = numFrameSamples * (_frameCapacity + 1);
|
||||
|
||||
if (numFrameSamples) {
|
||||
_buffer = new int16_t[_bufferLength];
|
||||
memset(_buffer, 0, _bufferLength * sizeof(int16_t));
|
||||
_buffer = new Sample[_bufferLength];
|
||||
memset(_buffer, 0, _bufferLength * SampleSize);
|
||||
} else {
|
||||
_buffer = nullptr;
|
||||
}
|
||||
|
@ -73,17 +78,29 @@ void AudioRingBuffer::resizeForFrameSize(int numFrameSamples) {
|
|||
reset();
|
||||
}
|
||||
|
||||
int AudioRingBuffer::readSamples(int16_t* destination, int maxSamples) {
|
||||
return readData((char*)destination, maxSamples * sizeof(int16_t)) / sizeof(int16_t);
|
||||
template <class T>
|
||||
int AudioRingBufferTemplate<T>::readSamples(Sample* destination, int maxSamples) {
|
||||
return readData((char*)destination, maxSamples * SampleSize) / SampleSize;
|
||||
}
|
||||
|
||||
int AudioRingBuffer::writeSamples(const int16_t* source, int maxSamples) {
|
||||
return writeData((char*)source, maxSamples * sizeof(int16_t)) / sizeof(int16_t);
|
||||
template <class T>
|
||||
int AudioRingBufferTemplate<T>::appendSamples(Sample* destination, int maxSamples, bool append) {
|
||||
if (append) {
|
||||
return appendData((char*)destination, maxSamples * SampleSize) / SampleSize;
|
||||
} else {
|
||||
return readData((char*)destination, maxSamples * SampleSize) / SampleSize;
|
||||
}
|
||||
}
|
||||
|
||||
int AudioRingBuffer::readData(char *data, int maxSize) {
|
||||
template <class T>
|
||||
int AudioRingBufferTemplate<T>::writeSamples(const Sample* source, int maxSamples) {
|
||||
return writeData((char*)source, maxSamples * SampleSize) / SampleSize;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
int AudioRingBufferTemplate<T>::readData(char *data, int maxSize) {
|
||||
// only copy up to the number of samples we have available
|
||||
int maxSamples = maxSize / sizeof(int16_t);
|
||||
int maxSamples = maxSize / SampleSize;
|
||||
int numReadSamples = std::min(maxSamples, samplesAvailable());
|
||||
|
||||
if (_nextOutput + numReadSamples > _buffer + _bufferLength) {
|
||||
|
@ -91,22 +108,56 @@ int AudioRingBuffer::readData(char *data, int maxSize) {
|
|||
int numSamplesToEnd = (_buffer + _bufferLength) - _nextOutput;
|
||||
|
||||
// read to the end of the buffer
|
||||
memcpy(data, _nextOutput, numSamplesToEnd * sizeof(int16_t));
|
||||
memcpy(data, _nextOutput, numSamplesToEnd * SampleSize);
|
||||
|
||||
// read the rest from the beginning of the buffer
|
||||
memcpy(data + (numSamplesToEnd * sizeof(int16_t)), _buffer, (numReadSamples - numSamplesToEnd) * sizeof(int16_t));
|
||||
memcpy(data + (numSamplesToEnd * SampleSize), _buffer, (numReadSamples - numSamplesToEnd) * SampleSize);
|
||||
} else {
|
||||
memcpy(data, _nextOutput, numReadSamples * sizeof(int16_t));
|
||||
memcpy(data, _nextOutput, numReadSamples * SampleSize);
|
||||
}
|
||||
|
||||
shiftReadPosition(numReadSamples);
|
||||
|
||||
return numReadSamples * sizeof(int16_t);
|
||||
return numReadSamples * SampleSize;
|
||||
}
|
||||
|
||||
int AudioRingBuffer::writeData(const char* data, int maxSize) {
|
||||
template <class T>
|
||||
int AudioRingBufferTemplate<T>::appendData(char *data, int maxSize) {
|
||||
// only copy up to the number of samples we have available
|
||||
int maxSamples = maxSize / SampleSize;
|
||||
int numReadSamples = std::min(maxSamples, samplesAvailable());
|
||||
|
||||
Sample* dest = reinterpret_cast<Sample*>(data);
|
||||
Sample* output = _nextOutput;
|
||||
if (_nextOutput + numReadSamples > _buffer + _bufferLength) {
|
||||
// we're going to need to do two reads to get this data, it wraps around the edge
|
||||
int numSamplesToEnd = (_buffer + _bufferLength) - _nextOutput;
|
||||
|
||||
// read to the end of the buffer
|
||||
for (int i = 0; i < numSamplesToEnd; i++) {
|
||||
*dest++ += *output++;
|
||||
}
|
||||
|
||||
// read the rest from the beginning of the buffer
|
||||
output = _buffer;
|
||||
for (int i = 0; i < (numReadSamples - numSamplesToEnd); i++) {
|
||||
*dest++ += *output++;
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < numReadSamples; i++) {
|
||||
*dest++ += *output++;
|
||||
}
|
||||
}
|
||||
|
||||
shiftReadPosition(numReadSamples);
|
||||
|
||||
return numReadSamples * SampleSize;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
int AudioRingBufferTemplate<T>::writeData(const char* data, int maxSize) {
|
||||
// only copy up to the number of samples we have capacity for
|
||||
int maxSamples = maxSize / sizeof(int16_t);
|
||||
int maxSamples = maxSize / SampleSize;
|
||||
int numWriteSamples = std::min(maxSamples, _sampleCapacity);
|
||||
int samplesRoomFor = _sampleCapacity - samplesAvailable();
|
||||
|
||||
|
@ -124,20 +175,21 @@ int AudioRingBuffer::writeData(const char* data, int maxSize) {
|
|||
int numSamplesToEnd = (_buffer + _bufferLength) - _endOfLastWrite;
|
||||
|
||||
// write to the end of the buffer
|
||||
memcpy(_endOfLastWrite, data, numSamplesToEnd * sizeof(int16_t));
|
||||
memcpy(_endOfLastWrite, data, numSamplesToEnd * SampleSize);
|
||||
|
||||
// write the rest to the beginning of the buffer
|
||||
memcpy(_buffer, data + (numSamplesToEnd * sizeof(int16_t)), (numWriteSamples - numSamplesToEnd) * sizeof(int16_t));
|
||||
memcpy(_buffer, data + (numSamplesToEnd * SampleSize), (numWriteSamples - numSamplesToEnd) * SampleSize);
|
||||
} else {
|
||||
memcpy(_endOfLastWrite, data, numWriteSamples * sizeof(int16_t));
|
||||
memcpy(_endOfLastWrite, data, numWriteSamples * SampleSize);
|
||||
}
|
||||
|
||||
_endOfLastWrite = shiftedPositionAccomodatingWrap(_endOfLastWrite, numWriteSamples);
|
||||
|
||||
return numWriteSamples * sizeof(int16_t);
|
||||
return numWriteSamples * SampleSize;
|
||||
}
|
||||
|
||||
int AudioRingBuffer::samplesAvailable() const {
|
||||
template <class T>
|
||||
int AudioRingBufferTemplate<T>::samplesAvailable() const {
|
||||
if (!_endOfLastWrite) {
|
||||
return 0;
|
||||
}
|
||||
|
@ -149,31 +201,8 @@ int AudioRingBuffer::samplesAvailable() const {
|
|||
return sampleDifference;
|
||||
}
|
||||
|
||||
int AudioRingBuffer::addSilentSamples(int silentSamples) {
|
||||
// NOTE: This implementation is nearly identical to writeData save for s/memcpy/memset, refer to comments there
|
||||
int numWriteSamples = std::min(silentSamples, _sampleCapacity);
|
||||
int samplesRoomFor = _sampleCapacity - samplesAvailable();
|
||||
|
||||
if (numWriteSamples > samplesRoomFor) {
|
||||
numWriteSamples = samplesRoomFor;
|
||||
|
||||
qCDebug(audio) << qPrintable(DROPPED_SILENT_DEBUG);
|
||||
}
|
||||
|
||||
if (_endOfLastWrite + numWriteSamples > _buffer + _bufferLength) {
|
||||
int numSamplesToEnd = (_buffer + _bufferLength) - _endOfLastWrite;
|
||||
memset(_endOfLastWrite, 0, numSamplesToEnd * sizeof(int16_t));
|
||||
memset(_buffer, 0, (numWriteSamples - numSamplesToEnd) * sizeof(int16_t));
|
||||
} else {
|
||||
memset(_endOfLastWrite, 0, numWriteSamples * sizeof(int16_t));
|
||||
}
|
||||
|
||||
_endOfLastWrite = shiftedPositionAccomodatingWrap(_endOfLastWrite, numWriteSamples);
|
||||
|
||||
return numWriteSamples;
|
||||
}
|
||||
|
||||
int16_t* AudioRingBuffer::shiftedPositionAccomodatingWrap(int16_t* position, int numSamplesShift) const {
|
||||
template <class T>
|
||||
typename AudioRingBufferTemplate<T>::Sample* AudioRingBufferTemplate<T>::shiftedPositionAccomodatingWrap(Sample* position, int numSamplesShift) const {
|
||||
// NOTE: It is possible to shift out-of-bounds if (|numSamplesShift| > 2 * _bufferLength), but this should not occur
|
||||
if (numSamplesShift > 0 && position + numSamplesShift >= _buffer + _bufferLength) {
|
||||
// this shift will wrap the position around to the beginning of the ring
|
||||
|
@ -186,11 +215,37 @@ int16_t* AudioRingBuffer::shiftedPositionAccomodatingWrap(int16_t* position, int
|
|||
}
|
||||
}
|
||||
|
||||
float AudioRingBuffer::getFrameLoudness(const int16_t* frameStart) const {
|
||||
template <class T>
|
||||
int AudioRingBufferTemplate<T>::addSilentSamples(int silentSamples) {
|
||||
// NOTE: This implementation is nearly identical to writeData save for s/memcpy/memset, refer to comments there
|
||||
int numWriteSamples = std::min(silentSamples, _sampleCapacity);
|
||||
int samplesRoomFor = _sampleCapacity - samplesAvailable();
|
||||
|
||||
if (numWriteSamples > samplesRoomFor) {
|
||||
numWriteSamples = samplesRoomFor;
|
||||
|
||||
qCDebug(audio) << qPrintable(DROPPED_SILENT_DEBUG);
|
||||
}
|
||||
|
||||
if (_endOfLastWrite + numWriteSamples > _buffer + _bufferLength) {
|
||||
int numSamplesToEnd = (_buffer + _bufferLength) - _endOfLastWrite;
|
||||
memset(_endOfLastWrite, 0, numSamplesToEnd * SampleSize);
|
||||
memset(_buffer, 0, (numWriteSamples - numSamplesToEnd) * SampleSize);
|
||||
} else {
|
||||
memset(_endOfLastWrite, 0, numWriteSamples * SampleSize);
|
||||
}
|
||||
|
||||
_endOfLastWrite = shiftedPositionAccomodatingWrap(_endOfLastWrite, numWriteSamples);
|
||||
|
||||
return numWriteSamples;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
float AudioRingBufferTemplate<T>::getFrameLoudness(const Sample* frameStart) const {
|
||||
// FIXME: This is a bad measure of loudness - normal estimation uses sqrt(sum(x*x))
|
||||
float loudness = 0.0f;
|
||||
const int16_t* sampleAt = frameStart;
|
||||
const int16_t* bufferLastAt = _buffer + _bufferLength - 1;
|
||||
const Sample* sampleAt = frameStart;
|
||||
const Sample* bufferLastAt = _buffer + _bufferLength - 1;
|
||||
|
||||
for (int i = 0; i < _numFrameSamples; ++i) {
|
||||
loudness += (float) std::abs(*sampleAt);
|
||||
|
@ -203,14 +258,16 @@ float AudioRingBuffer::getFrameLoudness(const int16_t* frameStart) const {
|
|||
return loudness;
|
||||
}
|
||||
|
||||
float AudioRingBuffer::getFrameLoudness(ConstIterator frameStart) const {
|
||||
template <class T>
|
||||
float AudioRingBufferTemplate<T>::getFrameLoudness(ConstIterator frameStart) const {
|
||||
if (frameStart.isNull()) {
|
||||
return 0.0f;
|
||||
}
|
||||
return getFrameLoudness(&(*frameStart));
|
||||
}
|
||||
|
||||
int AudioRingBuffer::writeSamples(ConstIterator source, int maxSamples) {
|
||||
template <class T>
|
||||
int AudioRingBufferTemplate<T>::writeSamples(ConstIterator source, int maxSamples) {
|
||||
int samplesToCopy = std::min(maxSamples, _sampleCapacity);
|
||||
int samplesRoomFor = _sampleCapacity - samplesAvailable();
|
||||
if (samplesToCopy > samplesRoomFor) {
|
||||
|
@ -221,7 +278,7 @@ int AudioRingBuffer::writeSamples(ConstIterator source, int maxSamples) {
|
|||
qCDebug(audio) << qPrintable(RING_BUFFER_OVERFLOW_DEBUG);
|
||||
}
|
||||
|
||||
int16_t* bufferLast = _buffer + _bufferLength - 1;
|
||||
Sample* bufferLast = _buffer + _bufferLength - 1;
|
||||
for (int i = 0; i < samplesToCopy; i++) {
|
||||
*_endOfLastWrite = *source;
|
||||
_endOfLastWrite = (_endOfLastWrite == bufferLast) ? _buffer : _endOfLastWrite + 1;
|
||||
|
@ -231,7 +288,8 @@ int AudioRingBuffer::writeSamples(ConstIterator source, int maxSamples) {
|
|||
return samplesToCopy;
|
||||
}
|
||||
|
||||
int AudioRingBuffer::writeSamplesWithFade(ConstIterator source, int maxSamples, float fade) {
|
||||
template <class T>
|
||||
int AudioRingBufferTemplate<T>::writeSamplesWithFade(ConstIterator source, int maxSamples, float fade) {
|
||||
int samplesToCopy = std::min(maxSamples, _sampleCapacity);
|
||||
int samplesRoomFor = _sampleCapacity - samplesAvailable();
|
||||
if (samplesToCopy > samplesRoomFor) {
|
||||
|
@ -242,12 +300,16 @@ int AudioRingBuffer::writeSamplesWithFade(ConstIterator source, int maxSamples,
|
|||
qCDebug(audio) << qPrintable(RING_BUFFER_OVERFLOW_DEBUG);
|
||||
}
|
||||
|
||||
int16_t* bufferLast = _buffer + _bufferLength - 1;
|
||||
Sample* bufferLast = _buffer + _bufferLength - 1;
|
||||
for (int i = 0; i < samplesToCopy; i++) {
|
||||
*_endOfLastWrite = (int16_t)((float)(*source) * fade);
|
||||
*_endOfLastWrite = (Sample)((float)(*source) * fade);
|
||||
_endOfLastWrite = (_endOfLastWrite == bufferLast) ? _buffer : _endOfLastWrite + 1;
|
||||
++source;
|
||||
}
|
||||
|
||||
return samplesToCopy;
|
||||
}
|
||||
|
||||
// explicit instantiations for scratch/mix buffers
|
||||
template class AudioRingBufferTemplate<int16_t>;
|
||||
template class AudioRingBufferTemplate<float>;
|
||||
|
|
|
@ -21,15 +21,19 @@
|
|||
|
||||
const int DEFAULT_RING_BUFFER_FRAME_CAPACITY = 10;
|
||||
|
||||
class AudioRingBuffer {
|
||||
template <class T>
|
||||
class AudioRingBufferTemplate {
|
||||
using Sample = T;
|
||||
static const int SampleSize = sizeof(Sample);
|
||||
|
||||
public:
|
||||
AudioRingBuffer(int numFrameSamples, int numFramesCapacity = DEFAULT_RING_BUFFER_FRAME_CAPACITY);
|
||||
~AudioRingBuffer();
|
||||
AudioRingBufferTemplate(int numFrameSamples, int numFramesCapacity = DEFAULT_RING_BUFFER_FRAME_CAPACITY);
|
||||
~AudioRingBufferTemplate();
|
||||
|
||||
// disallow copying
|
||||
AudioRingBuffer(const AudioRingBuffer&) = delete;
|
||||
AudioRingBuffer(AudioRingBuffer&&) = delete;
|
||||
AudioRingBuffer& operator=(const AudioRingBuffer&) = delete;
|
||||
AudioRingBufferTemplate(const AudioRingBufferTemplate&) = delete;
|
||||
AudioRingBufferTemplate(AudioRingBufferTemplate&&) = delete;
|
||||
AudioRingBufferTemplate& operator=(const AudioRingBufferTemplate&) = delete;
|
||||
|
||||
/// Invalidate any data in the buffer
|
||||
void clear();
|
||||
|
@ -41,13 +45,27 @@ public:
|
|||
// FIXME: discards any data in the buffer
|
||||
void resizeForFrameSize(int numFrameSamples);
|
||||
|
||||
// Reading and writing to the buffer uses minimal shared data, such that
|
||||
// in cases that avoid overwriting the buffer, a single producer/consumer
|
||||
// may use this as a lock-free pipe (see audio-client/src/AudioClient.cpp).
|
||||
// IMPORTANT: Avoid changes to the implementation that touch shared data unless you can
|
||||
// maintain this behavior.
|
||||
|
||||
/// Read up to maxSamples into destination (will only read up to samplesAvailable())
|
||||
/// Returns number of read samples
|
||||
int readSamples(int16_t* destination, int maxSamples);
|
||||
int readSamples(Sample* destination, int maxSamples);
|
||||
|
||||
/// Append up to maxSamples into destination (will only read up to samplesAvailable())
|
||||
/// If append == false, behaves as readSamples
|
||||
/// Returns number of appended samples
|
||||
int appendSamples(Sample* destination, int maxSamples, bool append = true);
|
||||
|
||||
/// Skip up to maxSamples (will only skip up to samplesAvailable())
|
||||
void skipSamples(int maxSamples) { shiftReadPosition(std::min(maxSamples, samplesAvailable())); }
|
||||
|
||||
/// Write up to maxSamples from source (will only write up to sample capacity)
|
||||
/// Returns number of written samples
|
||||
int writeSamples(const int16_t* source, int maxSamples);
|
||||
int writeSamples(const Sample* source, int maxSamples);
|
||||
|
||||
/// Write up to maxSamples silent samples (will only write until other data exists in the buffer)
|
||||
/// This method will not overwrite existing data in the buffer, instead dropping silent samples that would overflow
|
||||
|
@ -58,13 +76,17 @@ public:
|
|||
/// Returns number of read bytes
|
||||
int readData(char* destination, int maxSize);
|
||||
|
||||
/// Append up to maxSize into destination
|
||||
/// Returns number of read bytes
|
||||
int appendData(char* destination, int maxSize);
|
||||
|
||||
/// Write up to maxSize from source
|
||||
/// Returns number of written bytes
|
||||
int writeData(const char* source, int maxSize);
|
||||
|
||||
/// Returns a reference to the index-th sample offset from the current read sample
|
||||
int16_t& operator[](const int index) { return *shiftedPositionAccomodatingWrap(_nextOutput, index); }
|
||||
const int16_t& operator[] (const int index) const { return *shiftedPositionAccomodatingWrap(_nextOutput, index); }
|
||||
Sample& operator[](const int index) { return *shiftedPositionAccomodatingWrap(_nextOutput, index); }
|
||||
const Sample& operator[] (const int index) const { return *shiftedPositionAccomodatingWrap(_nextOutput, index); }
|
||||
|
||||
/// Essentially discards the next numSamples from the ring buffer
|
||||
/// NOTE: This is not checked - it is possible to shift past written data
|
||||
|
@ -84,41 +106,104 @@ public:
|
|||
|
||||
class ConstIterator {
|
||||
public:
|
||||
ConstIterator();
|
||||
ConstIterator(int16_t* bufferFirst, int capacity, int16_t* at);
|
||||
ConstIterator() :
|
||||
_bufferLength(0),
|
||||
_bufferFirst(NULL),
|
||||
_bufferLast(NULL),
|
||||
_at(NULL) {}
|
||||
ConstIterator(Sample* bufferFirst, int capacity, Sample* at) :
|
||||
_bufferLength(capacity),
|
||||
_bufferFirst(bufferFirst),
|
||||
_bufferLast(bufferFirst + capacity - 1),
|
||||
_at(at) {}
|
||||
ConstIterator(const ConstIterator& rhs) = default;
|
||||
|
||||
bool isNull() const { return _at == NULL; }
|
||||
|
||||
bool operator==(const ConstIterator& rhs) { return _at == rhs._at; }
|
||||
bool operator!=(const ConstIterator& rhs) { return _at != rhs._at; }
|
||||
const int16_t& operator*() { return *_at; }
|
||||
const Sample& operator*() { return *_at; }
|
||||
|
||||
ConstIterator& operator=(const ConstIterator& rhs);
|
||||
ConstIterator& operator++();
|
||||
ConstIterator operator++(int);
|
||||
ConstIterator& operator--();
|
||||
ConstIterator operator--(int);
|
||||
const int16_t& operator[] (int i);
|
||||
ConstIterator operator+(int i);
|
||||
ConstIterator operator-(int i);
|
||||
ConstIterator& operator=(const ConstIterator& rhs) {
|
||||
_bufferLength = rhs._bufferLength;
|
||||
_bufferFirst = rhs._bufferFirst;
|
||||
_bufferLast = rhs._bufferLast;
|
||||
_at = rhs._at;
|
||||
return *this;
|
||||
}
|
||||
ConstIterator& operator++() {
|
||||
_at = (_at == _bufferLast) ? _bufferFirst : _at + 1;
|
||||
return *this;
|
||||
}
|
||||
ConstIterator operator++(int) {
|
||||
ConstIterator tmp(*this);
|
||||
++(*this);
|
||||
return tmp;
|
||||
}
|
||||
ConstIterator& operator--() {
|
||||
_at = (_at == _bufferFirst) ? _bufferLast : _at - 1;
|
||||
return *this;
|
||||
}
|
||||
ConstIterator operator--(int) {
|
||||
ConstIterator tmp(*this);
|
||||
--(*this);
|
||||
return tmp;
|
||||
}
|
||||
const Sample& operator[] (int i) {
|
||||
return *atShiftedBy(i);
|
||||
}
|
||||
ConstIterator operator+(int i) {
|
||||
return ConstIterator(_bufferFirst, _bufferLength, atShiftedBy(i));
|
||||
}
|
||||
ConstIterator operator-(int i) {
|
||||
return ConstIterator(_bufferFirst, _bufferLength, atShiftedBy(-i));
|
||||
}
|
||||
|
||||
void readSamples(Sample* dest, int numSamples) {
|
||||
auto samplesToEnd = _bufferLast - _at + 1;
|
||||
|
||||
if (samplesToEnd >= numSamples) {
|
||||
memcpy(dest, _at, numSamples * SampleSize);
|
||||
_at += numSamples;
|
||||
} else {
|
||||
auto samplesFromStart = numSamples - samplesToEnd;
|
||||
memcpy(dest, _at, samplesToEnd * SampleSize);
|
||||
memcpy(dest + samplesToEnd, _bufferFirst, samplesFromStart * SampleSize);
|
||||
|
||||
_at = _bufferFirst + samplesFromStart;
|
||||
}
|
||||
}
|
||||
void readSamplesWithFade(Sample* dest, int numSamples, float fade) {
|
||||
Sample* at = _at;
|
||||
for (int i = 0; i < numSamples; i++) {
|
||||
*dest = (float)*at * fade;
|
||||
++dest;
|
||||
at = (at == _bufferLast) ? _bufferFirst : at + 1;
|
||||
}
|
||||
}
|
||||
|
||||
void readSamples(int16_t* dest, int numSamples);
|
||||
void readSamplesWithFade(int16_t* dest, int numSamples, float fade);
|
||||
void readSamplesWithUpmix(int16_t* dest, int numSamples, int numExtraChannels);
|
||||
void readSamplesWithDownmix(int16_t* dest, int numSamples);
|
||||
|
||||
private:
|
||||
int16_t* atShiftedBy(int i);
|
||||
Sample* atShiftedBy(int i) {
|
||||
i = (_at - _bufferFirst + i) % _bufferLength;
|
||||
if (i < 0) {
|
||||
i += _bufferLength;
|
||||
}
|
||||
return _bufferFirst + i;
|
||||
}
|
||||
|
||||
int _bufferLength;
|
||||
int16_t* _bufferFirst;
|
||||
int16_t* _bufferLast;
|
||||
int16_t* _at;
|
||||
Sample* _bufferFirst;
|
||||
Sample* _bufferLast;
|
||||
Sample* _at;
|
||||
};
|
||||
|
||||
ConstIterator nextOutput() const;
|
||||
ConstIterator lastFrameWritten() const;
|
||||
ConstIterator nextOutput() const {
|
||||
return ConstIterator(_buffer, _bufferLength, _nextOutput);
|
||||
}
|
||||
ConstIterator lastFrameWritten() const {
|
||||
return ConstIterator(_buffer, _bufferLength, _endOfLastWrite) - _numFrameSamples;
|
||||
}
|
||||
|
||||
int writeSamples(ConstIterator source, int maxSamples);
|
||||
int writeSamplesWithFade(ConstIterator source, int maxSamples, float fade);
|
||||
|
@ -126,8 +211,8 @@ public:
|
|||
float getFrameLoudness(ConstIterator frameStart) const;
|
||||
|
||||
protected:
|
||||
int16_t* shiftedPositionAccomodatingWrap(int16_t* position, int numSamplesShift) const;
|
||||
float getFrameLoudness(const int16_t* frameStart) const;
|
||||
Sample* shiftedPositionAccomodatingWrap(Sample* position, int numSamplesShift) const;
|
||||
float getFrameLoudness(const Sample* frameStart) const;
|
||||
|
||||
int _numFrameSamples;
|
||||
int _frameCapacity;
|
||||
|
@ -135,138 +220,13 @@ protected:
|
|||
int _bufferLength; // actual _buffer length (_sampleCapacity + 1)
|
||||
int _overflowCount{ 0 }; // times the ring buffer has overwritten data
|
||||
|
||||
int16_t* _nextOutput{ nullptr };
|
||||
int16_t* _endOfLastWrite{ nullptr };
|
||||
int16_t* _buffer{ nullptr };
|
||||
Sample* _nextOutput{ nullptr };
|
||||
Sample* _endOfLastWrite{ nullptr };
|
||||
Sample* _buffer{ nullptr };
|
||||
};
|
||||
|
||||
// inline the iterator:
|
||||
inline AudioRingBuffer::ConstIterator::ConstIterator() :
|
||||
_bufferLength(0),
|
||||
_bufferFirst(NULL),
|
||||
_bufferLast(NULL),
|
||||
_at(NULL) {}
|
||||
|
||||
inline AudioRingBuffer::ConstIterator::ConstIterator(int16_t* bufferFirst, int capacity, int16_t* at) :
|
||||
_bufferLength(capacity),
|
||||
_bufferFirst(bufferFirst),
|
||||
_bufferLast(bufferFirst + capacity - 1),
|
||||
_at(at) {}
|
||||
|
||||
inline AudioRingBuffer::ConstIterator& AudioRingBuffer::ConstIterator::operator=(const ConstIterator& rhs) {
|
||||
_bufferLength = rhs._bufferLength;
|
||||
_bufferFirst = rhs._bufferFirst;
|
||||
_bufferLast = rhs._bufferLast;
|
||||
_at = rhs._at;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline AudioRingBuffer::ConstIterator& AudioRingBuffer::ConstIterator::operator++() {
|
||||
_at = (_at == _bufferLast) ? _bufferFirst : _at + 1;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline AudioRingBuffer::ConstIterator AudioRingBuffer::ConstIterator::operator++(int) {
|
||||
ConstIterator tmp(*this);
|
||||
++(*this);
|
||||
return tmp;
|
||||
}
|
||||
|
||||
inline AudioRingBuffer::ConstIterator& AudioRingBuffer::ConstIterator::operator--() {
|
||||
_at = (_at == _bufferFirst) ? _bufferLast : _at - 1;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline AudioRingBuffer::ConstIterator AudioRingBuffer::ConstIterator::operator--(int) {
|
||||
ConstIterator tmp(*this);
|
||||
--(*this);
|
||||
return tmp;
|
||||
}
|
||||
|
||||
inline const int16_t& AudioRingBuffer::ConstIterator::operator[] (int i) {
|
||||
return *atShiftedBy(i);
|
||||
}
|
||||
|
||||
inline AudioRingBuffer::ConstIterator AudioRingBuffer::ConstIterator::operator+(int i) {
|
||||
return ConstIterator(_bufferFirst, _bufferLength, atShiftedBy(i));
|
||||
}
|
||||
|
||||
inline AudioRingBuffer::ConstIterator AudioRingBuffer::ConstIterator::operator-(int i) {
|
||||
return ConstIterator(_bufferFirst, _bufferLength, atShiftedBy(-i));
|
||||
}
|
||||
|
||||
inline int16_t* AudioRingBuffer::ConstIterator::atShiftedBy(int i) {
|
||||
i = (_at - _bufferFirst + i) % _bufferLength;
|
||||
if (i < 0) {
|
||||
i += _bufferLength;
|
||||
}
|
||||
return _bufferFirst + i;
|
||||
}
|
||||
|
||||
inline void AudioRingBuffer::ConstIterator::readSamples(int16_t* dest, int numSamples) {
|
||||
auto samplesToEnd = _bufferLast - _at + 1;
|
||||
|
||||
if (samplesToEnd >= numSamples) {
|
||||
memcpy(dest, _at, numSamples * sizeof(int16_t));
|
||||
_at += numSamples;
|
||||
} else {
|
||||
auto samplesFromStart = numSamples - samplesToEnd;
|
||||
memcpy(dest, _at, samplesToEnd * sizeof(int16_t));
|
||||
memcpy(dest + samplesToEnd, _bufferFirst, samplesFromStart * sizeof(int16_t));
|
||||
|
||||
_at = _bufferFirst + samplesFromStart;
|
||||
}
|
||||
}
|
||||
|
||||
inline void AudioRingBuffer::ConstIterator::readSamplesWithFade(int16_t* dest, int numSamples, float fade) {
|
||||
int16_t* at = _at;
|
||||
for (int i = 0; i < numSamples; i++) {
|
||||
*dest = (float)*at * fade;
|
||||
++dest;
|
||||
at = (at == _bufferLast) ? _bufferFirst : at + 1;
|
||||
}
|
||||
}
|
||||
|
||||
inline void AudioRingBuffer::ConstIterator::readSamplesWithUpmix(int16_t* dest, int numSamples, int numExtraChannels) {
|
||||
int16_t* at = _at;
|
||||
for (int i = 0; i < numSamples/2; i++) {
|
||||
|
||||
// read 2 samples
|
||||
int16_t left = *at;
|
||||
at = (at == _bufferLast) ? _bufferFirst : at + 1;
|
||||
int16_t right = *at;
|
||||
at = (at == _bufferLast) ? _bufferFirst : at + 1;
|
||||
|
||||
// write 2 + N samples
|
||||
*dest++ = left;
|
||||
*dest++ = right;
|
||||
for (int n = 0; n < numExtraChannels; n++) {
|
||||
*dest++ = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline void AudioRingBuffer::ConstIterator::readSamplesWithDownmix(int16_t* dest, int numSamples) {
|
||||
int16_t* at = _at;
|
||||
for (int i = 0; i < numSamples/2; i++) {
|
||||
|
||||
// read 2 samples
|
||||
int16_t left = *at;
|
||||
at = (at == _bufferLast) ? _bufferFirst : at + 1;
|
||||
int16_t right = *at;
|
||||
at = (at == _bufferLast) ? _bufferFirst : at + 1;
|
||||
|
||||
// write 1 sample
|
||||
*dest++ = (int16_t)((left + right) / 2);
|
||||
}
|
||||
}
|
||||
|
||||
inline AudioRingBuffer::ConstIterator AudioRingBuffer::nextOutput() const {
|
||||
return ConstIterator(_buffer, _bufferLength, _nextOutput);
|
||||
}
|
||||
|
||||
inline AudioRingBuffer::ConstIterator AudioRingBuffer::lastFrameWritten() const {
|
||||
return ConstIterator(_buffer, _bufferLength, _endOfLastWrite) - _numFrameSamples;
|
||||
}
|
||||
// expose explicit instantiations for scratch/mix buffers
|
||||
using AudioRingBuffer = AudioRingBufferTemplate<int16_t>;
|
||||
using AudioMixRingBuffer = AudioRingBufferTemplate<float>;
|
||||
|
||||
#endif // hifi_AudioRingBuffer_h
|
||||
|
|
|
@ -1304,6 +1304,9 @@ int AvatarData::getFauxJointIndex(const QString& name) const {
|
|||
if (name == "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND") {
|
||||
return CAMERA_RELATIVE_CONTROLLER_RIGHTHAND_INDEX;
|
||||
}
|
||||
if (name == "_CAMERA_MATRIX") {
|
||||
return CAMERA_MATRIX_INDEX;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
@ -1739,7 +1742,7 @@ static const QString JSON_AVATAR_HEAD_MODEL = QStringLiteral("headModel");
|
|||
static const QString JSON_AVATAR_BODY_MODEL = QStringLiteral("bodyModel");
|
||||
static const QString JSON_AVATAR_DISPLAY_NAME = QStringLiteral("displayName");
|
||||
// It isn't meaningful to persist sessionDisplayName.
|
||||
static const QString JSON_AVATAR_ATTACHEMENTS = QStringLiteral("attachments");
|
||||
static const QString JSON_AVATAR_ATTACHMENTS = QStringLiteral("attachments");
|
||||
static const QString JSON_AVATAR_ENTITIES = QStringLiteral("attachedEntities");
|
||||
static const QString JSON_AVATAR_SCALE = QStringLiteral("scale");
|
||||
static const QString JSON_AVATAR_VERSION = QStringLiteral("version");
|
||||
|
@ -1782,7 +1785,7 @@ QJsonObject AvatarData::toJson() const {
|
|||
for (auto attachment : getAttachmentData()) {
|
||||
attachmentsJson.push_back(attachment.toJson());
|
||||
}
|
||||
root[JSON_AVATAR_ATTACHEMENTS] = attachmentsJson;
|
||||
root[JSON_AVATAR_ATTACHMENTS] = attachmentsJson;
|
||||
}
|
||||
|
||||
_avatarEntitiesLock.withReadLock([&] {
|
||||
|
@ -1895,8 +1898,8 @@ void AvatarData::fromJson(const QJsonObject& json) {
|
|||
setTargetScale((float)json[JSON_AVATAR_SCALE].toDouble());
|
||||
}
|
||||
|
||||
if (json.contains(JSON_AVATAR_ATTACHEMENTS) && json[JSON_AVATAR_ATTACHEMENTS].isArray()) {
|
||||
QJsonArray attachmentsJson = json[JSON_AVATAR_ATTACHEMENTS].toArray();
|
||||
if (json.contains(JSON_AVATAR_ATTACHMENTS) && json[JSON_AVATAR_ATTACHMENTS].isArray()) {
|
||||
QJsonArray attachmentsJson = json[JSON_AVATAR_ATTACHMENTS].toArray();
|
||||
QVector<AttachmentData> attachments;
|
||||
for (auto attachmentJson : attachmentsJson) {
|
||||
AttachmentData attachment;
|
||||
|
@ -1907,7 +1910,7 @@ void AvatarData::fromJson(const QJsonObject& json) {
|
|||
}
|
||||
|
||||
// if (json.contains(JSON_AVATAR_ENTITIES) && json[JSON_AVATAR_ENTITIES].isArray()) {
|
||||
// QJsonArray attachmentsJson = json[JSON_AVATAR_ATTACHEMENTS].toArray();
|
||||
// QJsonArray attachmentsJson = json[JSON_AVATAR_ATTACHMENTS].toArray();
|
||||
// for (auto attachmentJson : attachmentsJson) {
|
||||
// // TODO -- something
|
||||
// }
|
||||
|
|
|
@ -765,5 +765,6 @@ const int CONTROLLER_RIGHTHAND_INDEX = 65533; // -3
|
|||
const int CONTROLLER_LEFTHAND_INDEX = 65532; // -4
|
||||
const int CAMERA_RELATIVE_CONTROLLER_RIGHTHAND_INDEX = 65531; // -5
|
||||
const int CAMERA_RELATIVE_CONTROLLER_LEFTHAND_INDEX = 65530; // -6
|
||||
const int CAMERA_MATRIX_INDEX = 65529; // -7
|
||||
|
||||
#endif // hifi_AvatarData_h
|
||||
|
|
|
@ -956,68 +956,29 @@ void EntityTreeRenderer::checkAndCallPreload(const EntityItemID& entityID, const
|
|||
}
|
||||
}
|
||||
|
||||
bool EntityTreeRenderer::isCollisionOwner(const QUuid& myNodeID, EntityTreePointer entityTree,
|
||||
const EntityItemID& id, const Collision& collision) {
|
||||
EntityItemPointer entity = entityTree->findEntityByEntityItemID(id);
|
||||
if (!entity) {
|
||||
return false;
|
||||
}
|
||||
QUuid simulatorID = entity->getSimulatorID();
|
||||
if (simulatorID.isNull()) {
|
||||
// Can be null if it has never moved since being created or coming out of persistence.
|
||||
// However, for there to be a collission, one of the two objects must be moving.
|
||||
const EntityItemID& otherID = (id == collision.idA) ? collision.idB : collision.idA;
|
||||
EntityItemPointer otherEntity = entityTree->findEntityByEntityItemID(otherID);
|
||||
if (!otherEntity) {
|
||||
return false;
|
||||
}
|
||||
simulatorID = otherEntity->getSimulatorID();
|
||||
}
|
||||
|
||||
if (simulatorID.isNull() || (simulatorID != myNodeID)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void EntityTreeRenderer::playEntityCollisionSound(const QUuid& myNodeID, EntityTreePointer entityTree,
|
||||
const EntityItemID& id, const Collision& collision) {
|
||||
|
||||
if (!isCollisionOwner(myNodeID, entityTree, id, collision)) {
|
||||
return;
|
||||
}
|
||||
|
||||
SharedSoundPointer collisionSound;
|
||||
float mass = 1.0; // value doesn't get used, but set it so compiler is quiet
|
||||
AACube minAACube;
|
||||
bool success = false;
|
||||
_tree->withReadLock([&] {
|
||||
EntityItemPointer entity = entityTree->findEntityByEntityItemID(id);
|
||||
if (entity) {
|
||||
collisionSound = entity->getCollisionSound();
|
||||
mass = entity->computeMass();
|
||||
minAACube = entity->getMinimumAACube(success);
|
||||
}
|
||||
});
|
||||
if (!success) {
|
||||
return;
|
||||
}
|
||||
void EntityTreeRenderer::playEntityCollisionSound(EntityItemPointer entity, const Collision& collision) {
|
||||
assert((bool)entity);
|
||||
SharedSoundPointer collisionSound = entity->getCollisionSound();
|
||||
if (!collisionSound) {
|
||||
return;
|
||||
}
|
||||
bool success = false;
|
||||
AACube minAACube = entity->getMinimumAACube(success);
|
||||
if (!success) {
|
||||
return;
|
||||
}
|
||||
float mass = entity->computeMass();
|
||||
|
||||
const float COLLISION_PENETRATION_TO_VELOCITY = 50; // as a subsitute for RELATIVE entity->getVelocity()
|
||||
const float COLLISION_PENETRATION_TO_VELOCITY = 50.0f; // as a subsitute for RELATIVE entity->getVelocity()
|
||||
// The collision.penetration is a pretty good indicator of changed velocity AFTER the initial contact,
|
||||
// but that first contact depends on exactly where we hit in the physics step.
|
||||
// We can get a more consistent initial-contact energy reading by using the changed velocity.
|
||||
// Note that velocityChange is not a good indicator for continuing collisions, because it does not distinguish
|
||||
// between bounce and sliding along a surface.
|
||||
const float linearVelocity = (collision.type == CONTACT_EVENT_TYPE_START) ?
|
||||
glm::length(collision.velocityChange) :
|
||||
glm::length(collision.penetration) * COLLISION_PENETRATION_TO_VELOCITY;
|
||||
const float energy = mass * linearVelocity * linearVelocity / 2.0f;
|
||||
const glm::vec3 position = collision.contactPoint;
|
||||
const float speedSquared = (collision.type == CONTACT_EVENT_TYPE_START) ?
|
||||
glm::length2(collision.velocityChange) :
|
||||
glm::length2(collision.penetration) * COLLISION_PENETRATION_TO_VELOCITY;
|
||||
const float energy = mass * speedSquared / 2.0f;
|
||||
const float COLLISION_ENERGY_AT_FULL_VOLUME = (collision.type == CONTACT_EVENT_TYPE_START) ? 150.0f : 5.0f;
|
||||
const float COLLISION_MINIMUM_VOLUME = 0.005f;
|
||||
const float energyFactorOfFull = fmin(1.0f, energy / COLLISION_ENERGY_AT_FULL_VOLUME);
|
||||
|
@ -1031,7 +992,7 @@ void EntityTreeRenderer::playEntityCollisionSound(const QUuid& myNodeID, EntityT
|
|||
// Shift the pitch down by ln(1 + (size / COLLISION_SIZE_FOR_STANDARD_PITCH)) / ln(2)
|
||||
const float COLLISION_SIZE_FOR_STANDARD_PITCH = 0.2f;
|
||||
const float stretchFactor = log(1.0f + (minAACube.getLargestDimension() / COLLISION_SIZE_FOR_STANDARD_PITCH)) / log(2);
|
||||
AudioInjector::playSound(collisionSound, volume, stretchFactor, position);
|
||||
AudioInjector::playSound(collisionSound, volume, stretchFactor, collision.contactPoint);
|
||||
}
|
||||
|
||||
void EntityTreeRenderer::entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB,
|
||||
|
@ -1041,30 +1002,28 @@ void EntityTreeRenderer::entityCollisionWithEntity(const EntityItemID& idA, cons
|
|||
if (!_tree || _shuttingDown) {
|
||||
return;
|
||||
}
|
||||
// Don't respond to small continuous contacts.
|
||||
const float COLLISION_MINUMUM_PENETRATION = 0.002f;
|
||||
if ((collision.type == CONTACT_EVENT_TYPE_CONTINUE) && (glm::length(collision.penetration) < COLLISION_MINUMUM_PENETRATION)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// See if we should play sounds
|
||||
EntityTreePointer entityTree = std::static_pointer_cast<EntityTree>(_tree);
|
||||
const QUuid& myNodeID = DependencyManager::get<NodeList>()->getSessionUUID();
|
||||
playEntityCollisionSound(myNodeID, entityTree, idA, collision);
|
||||
playEntityCollisionSound(myNodeID, entityTree, idB, collision);
|
||||
|
||||
// And now the entity scripts
|
||||
if (isCollisionOwner(myNodeID, entityTree, idA, collision)) {
|
||||
// trigger scripted collision sounds and events for locally owned objects
|
||||
EntityItemPointer entityA = entityTree->findEntityByEntityItemID(idA);
|
||||
if ((bool)entityA && myNodeID == entityA->getSimulatorID()) {
|
||||
playEntityCollisionSound(entityA, collision);
|
||||
emit collisionWithEntity(idA, idB, collision);
|
||||
if (_entitiesScriptEngine) {
|
||||
_entitiesScriptEngine->callEntityScriptMethod(idA, "collisionWithEntity", idB, collision);
|
||||
}
|
||||
}
|
||||
|
||||
if (isCollisionOwner(myNodeID, entityTree, idB, collision)) {
|
||||
emit collisionWithEntity(idB, idA, collision);
|
||||
EntityItemPointer entityB = entityTree->findEntityByEntityItemID(idB);
|
||||
if ((bool)entityB && myNodeID == entityB->getSimulatorID()) {
|
||||
playEntityCollisionSound(entityB, collision);
|
||||
// since we're swapping A and B we need to send the inverted collision
|
||||
Collision invertedCollision(collision);
|
||||
invertedCollision.invert();
|
||||
emit collisionWithEntity(idB, idA, invertedCollision);
|
||||
if (_entitiesScriptEngine) {
|
||||
_entitiesScriptEngine->callEntityScriptMethod(idB, "collisionWithEntity", idA, collision);
|
||||
_entitiesScriptEngine->callEntityScriptMethod(idB, "collisionWithEntity", idA, invertedCollision);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -170,11 +170,7 @@ private:
|
|||
bool _wantScripts;
|
||||
QSharedPointer<ScriptEngine> _entitiesScriptEngine;
|
||||
|
||||
bool isCollisionOwner(const QUuid& myNodeID, EntityTreePointer entityTree,
|
||||
const EntityItemID& id, const Collision& collision);
|
||||
|
||||
void playEntityCollisionSound(const QUuid& myNodeID, EntityTreePointer entityTree,
|
||||
const EntityItemID& id, const Collision& collision);
|
||||
static void playEntityCollisionSound(EntityItemPointer entity, const Collision& collision);
|
||||
|
||||
bool _lastPointerEventValid;
|
||||
PointerEvent _lastPointerEvent;
|
||||
|
|
|
@ -320,12 +320,9 @@ bool RenderableModelEntityItem::getAnimationFrame() {
|
|||
glm::mat4 finalMat = (translationMat * fbxJoints[index].preTransform *
|
||||
rotationMat * fbxJoints[index].postTransform);
|
||||
_localJointTranslations[j] = extractTranslation(finalMat);
|
||||
_localJointTranslationsSet[j] = true;
|
||||
_localJointTranslationsDirty[j] = true;
|
||||
|
||||
_localJointRotations[j] = glmExtractRotation(finalMat);
|
||||
|
||||
_localJointRotationsSet[j] = true;
|
||||
_localJointRotationsDirty[j] = true;
|
||||
}
|
||||
}
|
||||
|
@ -508,6 +505,7 @@ ModelPointer RenderableModelEntityItem::getModel(QSharedPointer<EntityTreeRender
|
|||
// If we don't have a model, allocate one *immediately*
|
||||
if (!_model) {
|
||||
_model = _myRenderer->allocateModel(getModelURL(), renderer->getEntityLoadingPriority(*this));
|
||||
_model->setSpatiallyNestableOverride(shared_from_this());
|
||||
_needsInitialSimulation = true;
|
||||
// If we need to change URLs, update it *after rendering* (to avoid access violations)
|
||||
} else if (QUrl(getModelURL()) != _model->getURL()) {
|
||||
|
@ -1192,8 +1190,7 @@ void RenderableModelEntityItem::locationChanged(bool tellPhysics) {
|
|||
PerformanceTimer pertTimer("locationChanged");
|
||||
EntityItem::locationChanged(tellPhysics);
|
||||
if (_model && _model->isActive()) {
|
||||
_model->setRotation(getRotation());
|
||||
_model->setTranslation(getPosition());
|
||||
_model->updateRenderItems();
|
||||
|
||||
void* key = (void*)this;
|
||||
std::weak_ptr<RenderableModelEntityItem> weakSelf =
|
||||
|
|
|
@ -18,12 +18,12 @@
|
|||
|
||||
#include <GeometryCache.h>
|
||||
#include <PerfStat.h>
|
||||
#include <gl/OffscreenQmlSurface.h>
|
||||
#include <AbstractViewStateInterface.h>
|
||||
#include <GLMHelpers.h>
|
||||
#include <PathUtils.h>
|
||||
#include <TextureCache.h>
|
||||
#include <gpu/Context.h>
|
||||
#include <TabletScriptingInterface.h>
|
||||
|
||||
#include "EntityTreeRenderer.h"
|
||||
#include "EntitiesRendererLogging.h"
|
||||
|
@ -46,6 +46,7 @@ EntityItemPointer RenderableWebEntityItem::factory(const EntityItemID& entityID,
|
|||
|
||||
RenderableWebEntityItem::RenderableWebEntityItem(const EntityItemID& entityItemID) :
|
||||
WebEntityItem(entityItemID) {
|
||||
|
||||
qCDebug(entities) << "Created web entity " << getID();
|
||||
|
||||
_touchDevice.setCapabilities(QTouchDevice::Position);
|
||||
|
@ -57,7 +58,9 @@ RenderableWebEntityItem::RenderableWebEntityItem(const EntityItemID& entityItemI
|
|||
|
||||
RenderableWebEntityItem::~RenderableWebEntityItem() {
|
||||
destroyWebSurface();
|
||||
|
||||
qCDebug(entities) << "Destroyed web entity " << getID();
|
||||
|
||||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
||||
if (geometryCache) {
|
||||
geometryCache->releaseID(_geometryId);
|
||||
|
@ -78,18 +81,19 @@ bool RenderableWebEntityItem::buildWebSurface(QSharedPointer<EntityTreeRenderer>
|
|||
QString createGlobalEventBridgeStr = QTextStream(&createGlobalEventBridgeFile).readAll();
|
||||
|
||||
// concatenate these js files
|
||||
javaScriptToInject = webChannelStr + createGlobalEventBridgeStr;
|
||||
_javaScriptToInject = webChannelStr + createGlobalEventBridgeStr;
|
||||
} else {
|
||||
qCWarning(entitiesrenderer) << "unable to find qwebchannel.js or createGlobalEventBridge.js";
|
||||
}
|
||||
|
||||
// Save the original GL context, because creating a QML surface will create a new context
|
||||
QOpenGLContext * currentContext = QOpenGLContext::currentContext();
|
||||
QOpenGLContext* currentContext = QOpenGLContext::currentContext();
|
||||
if (!currentContext) {
|
||||
return false;
|
||||
}
|
||||
|
||||
++_currentWebCount;
|
||||
|
||||
qCDebug(entities) << "Building web surface: " << getID() << ", #" << _currentWebCount << ", url = " << _sourceUrl;
|
||||
|
||||
QSurface * currentSurface = currentContext->surface();
|
||||
|
@ -113,13 +117,12 @@ bool RenderableWebEntityItem::buildWebSurface(QSharedPointer<EntityTreeRenderer>
|
|||
|
||||
// The lifetime of the QML surface MUST be managed by the main thread
|
||||
// Additionally, we MUST use local variables copied by value, rather than
|
||||
// member variables, since they would implicitly refer to a this that
|
||||
// member variables, since they would implicitly refer to a this that
|
||||
// is no longer valid
|
||||
_webSurface->create(currentContext);
|
||||
_webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/controls/"));
|
||||
_webSurface->load("WebView.qml", [&](QQmlContext* context, QObject* obj) {
|
||||
context->setContextProperty("eventBridgeJavaScriptToInject", QVariant(javaScriptToInject));
|
||||
});
|
||||
|
||||
loadSourceURL();
|
||||
|
||||
_webSurface->resume();
|
||||
_webSurface->getRootItem()->setProperty("url", _sourceUrl);
|
||||
_webSurface->getRootContext()->setContextProperty("desktop", QVariant());
|
||||
|
@ -156,7 +159,8 @@ bool RenderableWebEntityItem::buildWebSurface(QSharedPointer<EntityTreeRenderer>
|
|||
point.setPos(windowPoint);
|
||||
QList<QTouchEvent::TouchPoint> touchPoints;
|
||||
touchPoints.push_back(point);
|
||||
QTouchEvent* touchEvent = new QTouchEvent(QEvent::TouchEnd, nullptr, Qt::NoModifier, Qt::TouchPointReleased, touchPoints);
|
||||
QTouchEvent* touchEvent = new QTouchEvent(QEvent::TouchEnd, nullptr,
|
||||
Qt::NoModifier, Qt::TouchPointReleased, touchPoints);
|
||||
touchEvent->setWindow(_webSurface->getWindow());
|
||||
touchEvent->setDevice(&_touchDevice);
|
||||
touchEvent->setTarget(_webSurface->getRootItem());
|
||||
|
@ -237,14 +241,42 @@ void RenderableWebEntityItem::render(RenderArgs* args) {
|
|||
|
||||
batch._glColor4f(1.0f, 1.0f, 1.0f, fadeRatio);
|
||||
|
||||
const bool IS_AA = true;
|
||||
if (fadeRatio < OPAQUE_ALPHA_THRESHOLD) {
|
||||
DependencyManager::get<GeometryCache>()->bindTransparentWebBrowserProgram(batch);
|
||||
DependencyManager::get<GeometryCache>()->bindTransparentWebBrowserProgram(batch, IS_AA);
|
||||
} else {
|
||||
DependencyManager::get<GeometryCache>()->bindOpaqueWebBrowserProgram(batch);
|
||||
DependencyManager::get<GeometryCache>()->bindOpaqueWebBrowserProgram(batch, IS_AA);
|
||||
}
|
||||
DependencyManager::get<GeometryCache>()->renderQuad(batch, topLeft, bottomRight, texMin, texMax, glm::vec4(1.0f, 1.0f, 1.0f, fadeRatio), _geometryId);
|
||||
}
|
||||
|
||||
void RenderableWebEntityItem::loadSourceURL() {
|
||||
QUrl sourceUrl(_sourceUrl);
|
||||
if (sourceUrl.scheme() == "http" || sourceUrl.scheme() == "https" ||
|
||||
_sourceUrl.toLower().endsWith(".htm") || _sourceUrl.toLower().endsWith(".html")) {
|
||||
_contentType = htmlContent;
|
||||
_webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "qml/controls/"));
|
||||
_webSurface->load("WebView.qml", [&](QQmlContext* context, QObject* obj) {
|
||||
context->setContextProperty("eventBridgeJavaScriptToInject", QVariant(_javaScriptToInject));
|
||||
});
|
||||
_webSurface->getRootItem()->setProperty("url", _sourceUrl);
|
||||
_webSurface->getRootContext()->setContextProperty("desktop", QVariant());
|
||||
|
||||
} else {
|
||||
_contentType = qmlContent;
|
||||
_webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath()));
|
||||
_webSurface->load(_sourceUrl, [&](QQmlContext* context, QObject* obj) {});
|
||||
|
||||
if (_webSurface->getRootItem() && _webSurface->getRootItem()->objectName() == "tabletRoot") {
|
||||
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
|
||||
tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system",
|
||||
_webSurface->getRootItem(), _webSurface.data());
|
||||
}
|
||||
}
|
||||
_webSurface->getRootContext()->setContextProperty("globalPosition", vec3toVariant(getPosition()));
|
||||
}
|
||||
|
||||
|
||||
void RenderableWebEntityItem::setSourceUrl(const QString& value) {
|
||||
auto valueBeforeSuperclassSet = _sourceUrl;
|
||||
|
||||
|
@ -254,7 +286,10 @@ void RenderableWebEntityItem::setSourceUrl(const QString& value) {
|
|||
qCDebug(entities) << "Changing web entity source URL to " << _sourceUrl;
|
||||
|
||||
AbstractViewStateInterface::instance()->postLambdaEvent([this] {
|
||||
_webSurface->getRootItem()->setProperty("url", _sourceUrl);
|
||||
loadSourceURL();
|
||||
if (_contentType == htmlContent) {
|
||||
_webSurface->getRootItem()->setProperty("url", _sourceUrl);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -337,6 +372,14 @@ void RenderableWebEntityItem::destroyWebSurface() {
|
|||
--_currentWebCount;
|
||||
|
||||
QQuickItem* rootItem = _webSurface->getRootItem();
|
||||
|
||||
if (rootItem && rootItem->objectName() == "tabletRoot") {
|
||||
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
|
||||
tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", nullptr, nullptr);
|
||||
}
|
||||
|
||||
// Fix for crash in QtWebEngineCore when rapidly switching domains
|
||||
// Call stop on the QWebEngineView before destroying OffscreenQMLSurface.
|
||||
if (rootItem) {
|
||||
QObject* obj = rootItem->findChild<QObject*>("webEngineView");
|
||||
if (obj) {
|
||||
|
@ -363,6 +406,12 @@ void RenderableWebEntityItem::destroyWebSurface() {
|
|||
}
|
||||
|
||||
void RenderableWebEntityItem::update(const quint64& now) {
|
||||
|
||||
if (_webSurface) {
|
||||
// update globalPosition
|
||||
_webSurface->getRootContext()->setContextProperty("globalPosition", vec3toVariant(getPosition()));
|
||||
}
|
||||
|
||||
auto interval = now - _lastRenderTime;
|
||||
if (interval > MAX_NO_RENDER_INTERVAL) {
|
||||
destroyWebSurface();
|
||||
|
@ -374,6 +423,13 @@ bool RenderableWebEntityItem::isTransparent() {
|
|||
return fadeRatio < OPAQUE_ALPHA_THRESHOLD;
|
||||
}
|
||||
|
||||
QObject* RenderableWebEntityItem::getRootItem() {
|
||||
if (_webSurface) {
|
||||
return dynamic_cast<QObject*>(_webSurface->getRootItem());
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void RenderableWebEntityItem::emitScriptEvent(const QVariant& message) {
|
||||
if (_webSurface) {
|
||||
_webSurface->emitScriptEvent(message);
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include <gl/OffscreenQmlSurface.h>
|
||||
|
||||
#include <WebEntityItem.h>
|
||||
#include <gl/OffscreenQmlSurface.h>
|
||||
|
||||
#include "RenderableEntityItem.h"
|
||||
|
||||
|
@ -33,6 +34,7 @@ public:
|
|||
~RenderableWebEntityItem();
|
||||
|
||||
virtual void render(RenderArgs* args) override;
|
||||
void loadSourceURL();
|
||||
virtual void setSourceUrl(const QString& value) override;
|
||||
|
||||
virtual bool wantsHandControllerPointerEvents() const override { return true; }
|
||||
|
@ -51,6 +53,10 @@ public:
|
|||
|
||||
virtual bool isTransparent() override;
|
||||
|
||||
public:
|
||||
|
||||
virtual QObject* getRootItem() override;
|
||||
|
||||
private:
|
||||
bool buildWebSurface(QSharedPointer<EntityTreeRenderer> renderer);
|
||||
void destroyWebSurface();
|
||||
|
@ -67,6 +73,14 @@ private:
|
|||
QMetaObject::Connection _mouseReleaseConnection;
|
||||
QMetaObject::Connection _mouseMoveConnection;
|
||||
QMetaObject::Connection _hoverLeaveConnection;
|
||||
|
||||
QString _javaScriptToInject;
|
||||
|
||||
enum contentType {
|
||||
htmlContent,
|
||||
qmlContent
|
||||
};
|
||||
contentType _contentType;
|
||||
int _geometryId { 0 };
|
||||
};
|
||||
|
||||
|
|
|
@ -1327,7 +1327,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) {
|
|||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(href, setHref);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(description, setDescription);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(actionData, setActionData);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(parentID, setParentID);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(parentID, updateParentID);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(parentJointIndex, setParentJointIndex);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(queryAACube, setQueryAACube);
|
||||
|
||||
|
@ -1584,6 +1584,14 @@ void EntityItem::updatePosition(const glm::vec3& value) {
|
|||
}
|
||||
}
|
||||
|
||||
void EntityItem::updateParentID(const QUuid& value) {
|
||||
if (_parentID != value) {
|
||||
setParentID(value);
|
||||
_dirtyFlags |= Simulation::DIRTY_MOTION_TYPE; // children are forced to be kinematic
|
||||
_dirtyFlags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar
|
||||
}
|
||||
}
|
||||
|
||||
void EntityItem::updatePositionFromNetwork(const glm::vec3& value) {
|
||||
if (shouldSuppressLocationEdits()) {
|
||||
return;
|
||||
|
@ -1792,7 +1800,6 @@ void EntityItem::updateCreated(uint64_t value) {
|
|||
}
|
||||
|
||||
void EntityItem::computeCollisionGroupAndFinalMask(int16_t& group, int16_t& mask) const {
|
||||
// TODO: detect attachment status and adopt group of wearer
|
||||
if (_collisionless) {
|
||||
group = BULLET_COLLISION_GROUP_COLLISIONLESS;
|
||||
mask = 0;
|
||||
|
@ -1806,10 +1813,33 @@ void EntityItem::computeCollisionGroupAndFinalMask(int16_t& group, int16_t& mask
|
|||
}
|
||||
|
||||
uint8_t userMask = getCollisionMask();
|
||||
if (userMask & USER_COLLISION_GROUP_MY_AVATAR) {
|
||||
// if this entity is a descendant of MyAvatar, don't collide with MyAvatar. This avoids the
|
||||
// "bootstrapping" problem where you can shoot yourself across the room by grabbing something
|
||||
// and holding it against your own avatar.
|
||||
QUuid ancestorID = findAncestorOfType(NestableType::Avatar);
|
||||
if (!ancestorID.isNull() && ancestorID == Physics::getSessionUUID()) {
|
||||
userMask &= ~USER_COLLISION_GROUP_MY_AVATAR;
|
||||
}
|
||||
}
|
||||
if (userMask & USER_COLLISION_GROUP_MY_AVATAR) {
|
||||
// also, don't bootstrap our own avatar with a hold action
|
||||
QList<EntityActionPointer> holdActions = getActionsOfType(ACTION_TYPE_HOLD);
|
||||
QList<EntityActionPointer>::const_iterator i = holdActions.begin();
|
||||
while (i != holdActions.end()) {
|
||||
EntityActionPointer action = *i;
|
||||
if (action->isMine()) {
|
||||
userMask &= ~USER_COLLISION_GROUP_MY_AVATAR;
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
if ((bool)(userMask & USER_COLLISION_GROUP_MY_AVATAR) !=
|
||||
(bool)(userMask & USER_COLLISION_GROUP_OTHER_AVATAR)) {
|
||||
// asymmetric avatar collision mask bits
|
||||
if (!getSimulatorID().isNull() && (!getSimulatorID().isNull()) && getSimulatorID() != Physics::getSessionUUID()) {
|
||||
if (!getSimulatorID().isNull() && getSimulatorID() != Physics::getSessionUUID()) {
|
||||
// someone else owns the simulation, so we toggle the avatar bits (swap interpretation)
|
||||
userMask ^= USER_COLLISION_MASK_AVATARS | ~userMask;
|
||||
}
|
||||
|
@ -1908,6 +1938,7 @@ bool EntityItem::addActionInternal(EntitySimulationPointer simulation, EntityAct
|
|||
if (success) {
|
||||
_allActionsDataCache = newDataCache;
|
||||
_dirtyFlags |= Simulation::DIRTY_PHYSICS_ACTIVATION;
|
||||
_dirtyFlags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar
|
||||
} else {
|
||||
qCDebug(entities) << "EntityItem::addActionInternal -- serializeActions failed";
|
||||
}
|
||||
|
@ -1930,6 +1961,7 @@ bool EntityItem::updateAction(EntitySimulationPointer simulation, const QUuid& a
|
|||
action->setIsMine(true);
|
||||
serializeActions(success, _allActionsDataCache);
|
||||
_dirtyFlags |= Simulation::DIRTY_PHYSICS_ACTIVATION;
|
||||
_dirtyFlags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar
|
||||
} else {
|
||||
qCDebug(entities) << "EntityItem::updateAction failed";
|
||||
}
|
||||
|
@ -1967,6 +1999,7 @@ bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulationPoi
|
|||
bool success = true;
|
||||
serializeActions(success, _allActionsDataCache);
|
||||
_dirtyFlags |= Simulation::DIRTY_PHYSICS_ACTIVATION;
|
||||
_dirtyFlags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar
|
||||
setActionDataNeedsTransmit(true);
|
||||
return success;
|
||||
}
|
||||
|
@ -1987,6 +2020,7 @@ bool EntityItem::clearActions(EntitySimulationPointer simulation) {
|
|||
_actionsToRemove.clear();
|
||||
_allActionsDataCache.clear();
|
||||
_dirtyFlags |= Simulation::DIRTY_PHYSICS_ACTIVATION;
|
||||
_dirtyFlags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
@ -2193,7 +2227,7 @@ bool EntityItem::shouldSuppressLocationEdits() const {
|
|||
return false;
|
||||
}
|
||||
|
||||
QList<EntityActionPointer> EntityItem::getActionsOfType(EntityActionType typeToGet) {
|
||||
QList<EntityActionPointer> EntityItem::getActionsOfType(EntityActionType typeToGet) const {
|
||||
QList<EntityActionPointer> result;
|
||||
|
||||
QHash<QUuid, EntityActionPointer>::const_iterator i = _objectActions.begin();
|
||||
|
|
|
@ -343,6 +343,7 @@ public:
|
|||
// updateFoo() methods to be used when changes need to be accumulated in the _dirtyFlags
|
||||
virtual void updateRegistrationPoint(const glm::vec3& value);
|
||||
void updatePosition(const glm::vec3& value);
|
||||
void updateParentID(const QUuid& value);
|
||||
void updatePositionFromNetwork(const glm::vec3& value);
|
||||
void updateDimensions(const glm::vec3& value);
|
||||
void updateRotation(const glm::quat& rotation);
|
||||
|
@ -419,7 +420,7 @@ public:
|
|||
const QUuid& getSourceUUID() const { return _sourceUUID; }
|
||||
bool matchesSourceUUID(const QUuid& sourceUUID) const { return _sourceUUID == sourceUUID; }
|
||||
|
||||
QList<EntityActionPointer> getActionsOfType(EntityActionType typeToGet);
|
||||
QList<EntityActionPointer> getActionsOfType(EntityActionType typeToGet) const;
|
||||
|
||||
// these are in the frame of this object
|
||||
virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override { return glm::quat(); }
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#include "QVariantGLM.h"
|
||||
#include "SimulationOwner.h"
|
||||
#include "ZoneEntityItem.h"
|
||||
#include "WebEntityItem.h"
|
||||
#include <EntityScriptClient.h>
|
||||
|
||||
|
||||
|
@ -1366,14 +1367,16 @@ bool EntityScriptingInterface::isChildOfParent(QUuid childID, QUuid parentID) {
|
|||
|
||||
_entityTree->withReadLock([&] {
|
||||
EntityItemPointer parent = _entityTree->findEntityByEntityItemID(parentID);
|
||||
parent->forEachDescendant([&](SpatiallyNestablePointer descendant) {
|
||||
if(descendant->getID() == childID) {
|
||||
isChild = true;
|
||||
return;
|
||||
}
|
||||
});
|
||||
if (parent) {
|
||||
parent->forEachDescendant([&](SpatiallyNestablePointer descendant) {
|
||||
if (descendant->getID() == childID) {
|
||||
isChild = true;
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return isChild;
|
||||
}
|
||||
|
||||
|
@ -1397,7 +1400,8 @@ QVector<QUuid> EntityScriptingInterface::getChildrenIDsOfJoint(const QUuid& pare
|
|||
return;
|
||||
}
|
||||
parent->forEachChild([&](SpatiallyNestablePointer child) {
|
||||
if (child->getParentJointIndex() == jointIndex) {
|
||||
if (child->getParentJointIndex() == jointIndex &&
|
||||
child->getNestableType() != NestableType::Overlay) {
|
||||
result.push_back(child->getID());
|
||||
}
|
||||
});
|
||||
|
@ -1492,3 +1496,12 @@ void EntityScriptingInterface::setCostMultiplier(float value) {
|
|||
costMultiplier = value;
|
||||
}
|
||||
|
||||
QObject* EntityScriptingInterface::getWebViewRoot(const QUuid& entityID) {
|
||||
if (auto entity = checkForTreeEntityAndTypeMatch(entityID, EntityTypes::Web)) {
|
||||
auto webEntity = std::dynamic_pointer_cast<WebEntityItem>(entity);
|
||||
QObject* root = webEntity->getRootItem();
|
||||
return root;
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -285,6 +285,8 @@ public slots:
|
|||
|
||||
Q_INVOKABLE void emitScriptEvent(const EntityItemID& entityID, const QVariant& message);
|
||||
|
||||
Q_INVOKABLE QObject* getWebViewRoot(const QUuid& entityID);
|
||||
|
||||
signals:
|
||||
void collisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision);
|
||||
|
||||
|
|
|
@ -927,13 +927,21 @@ void EntityTree::initEntityEditFilterEngine(QScriptEngine* engine, std::function
|
|||
_entityEditFilterHadUncaughtExceptions = entityEditFilterHadUncaughtExceptions;
|
||||
auto global = _entityEditFilterEngine->globalObject();
|
||||
_entityEditFilterFunction = global.property("filter");
|
||||
_hasEntityEditFilter = _entityEditFilterFunction.isFunction();
|
||||
if (!_entityEditFilterFunction.isFunction()) {
|
||||
qCDebug(entities) << "Filter function specified but not found. Will reject all edits.";
|
||||
_entityEditFilterEngine = nullptr; // So that we don't try to call it. See filterProperties.
|
||||
}
|
||||
_hasEntityEditFilter = true;
|
||||
}
|
||||
|
||||
bool EntityTree::filterProperties(EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged) {
|
||||
if (!_hasEntityEditFilter || !_entityEditFilterEngine) {
|
||||
if (!_entityEditFilterEngine) {
|
||||
propertiesOut = propertiesIn;
|
||||
wasChanged = false; // not changed
|
||||
if (_hasEntityEditFilter) {
|
||||
qCDebug(entities) << "Rejecting properties because filter has not been set.";
|
||||
return false;
|
||||
}
|
||||
return true; // allowed
|
||||
}
|
||||
auto oldProperties = propertiesIn.getDesiredProperties();
|
||||
|
@ -1552,6 +1560,8 @@ bool EntityTree::sendEntitiesOperation(OctreeElementPointer element, void* extra
|
|||
return args->map->value(oldID);
|
||||
}
|
||||
EntityItemID newID = QUuid::createUuid();
|
||||
args->map->insert(oldID, newID);
|
||||
|
||||
EntityItemProperties properties = item->getProperties();
|
||||
EntityItemID oldParentID = properties.getParentID();
|
||||
if (oldParentID.isInvalidID()) { // no parent
|
||||
|
@ -1567,6 +1577,43 @@ bool EntityTree::sendEntitiesOperation(OctreeElementPointer element, void* extra
|
|||
}
|
||||
}
|
||||
|
||||
if (!properties.getXNNeighborID().isInvalidID()) {
|
||||
auto neighborEntity = args->ourTree->findEntityByEntityItemID(properties.getXNNeighborID());
|
||||
if (neighborEntity) {
|
||||
properties.setXNNeighborID(getMapped(neighborEntity));
|
||||
}
|
||||
}
|
||||
if (!properties.getXPNeighborID().isInvalidID()) {
|
||||
auto neighborEntity = args->ourTree->findEntityByEntityItemID(properties.getXPNeighborID());
|
||||
if (neighborEntity) {
|
||||
properties.setXPNeighborID(getMapped(neighborEntity));
|
||||
}
|
||||
}
|
||||
if (!properties.getYNNeighborID().isInvalidID()) {
|
||||
auto neighborEntity = args->ourTree->findEntityByEntityItemID(properties.getYNNeighborID());
|
||||
if (neighborEntity) {
|
||||
properties.setYNNeighborID(getMapped(neighborEntity));
|
||||
}
|
||||
}
|
||||
if (!properties.getYPNeighborID().isInvalidID()) {
|
||||
auto neighborEntity = args->ourTree->findEntityByEntityItemID(properties.getYPNeighborID());
|
||||
if (neighborEntity) {
|
||||
properties.setYPNeighborID(getMapped(neighborEntity));
|
||||
}
|
||||
}
|
||||
if (!properties.getZNNeighborID().isInvalidID()) {
|
||||
auto neighborEntity = args->ourTree->findEntityByEntityItemID(properties.getZNNeighborID());
|
||||
if (neighborEntity) {
|
||||
properties.setZNNeighborID(getMapped(neighborEntity));
|
||||
}
|
||||
}
|
||||
if (!properties.getZPNeighborID().isInvalidID()) {
|
||||
auto neighborEntity = args->ourTree->findEntityByEntityItemID(properties.getZPNeighborID());
|
||||
if (neighborEntity) {
|
||||
properties.setZPNeighborID(getMapped(neighborEntity));
|
||||
}
|
||||
}
|
||||
|
||||
// set creation time to "now" for imported entities
|
||||
properties.setCreated(usecTimestampNow());
|
||||
|
||||
|
@ -1584,7 +1631,6 @@ bool EntityTree::sendEntitiesOperation(OctreeElementPointer element, void* extra
|
|||
args->otherTree->addEntity(newID, properties);
|
||||
});
|
||||
}
|
||||
args->map->insert(oldID, newID);
|
||||
return newID;
|
||||
};
|
||||
|
||||
|
|
|
@ -267,6 +267,7 @@ public:
|
|||
void notifyNewCollisionSoundURL(const QString& newCollisionSoundURL, const EntityItemID& entityID);
|
||||
|
||||
void initEntityEditFilterEngine(QScriptEngine* engine, std::function<bool()> entityEditFilterHadUncaughtExceptions);
|
||||
void setHasEntityFilter(bool hasFilter) { _hasEntityEditFilter = hasFilter; }
|
||||
|
||||
static const float DEFAULT_MAX_TMP_ENTITY_LIFETIME;
|
||||
|
||||
|
|
|
@ -59,6 +59,8 @@ public:
|
|||
void setDPI(uint16_t value);
|
||||
uint16_t getDPI() const;
|
||||
|
||||
virtual QObject* getRootItem() { return nullptr; }
|
||||
|
||||
protected:
|
||||
QString _sourceUrl;
|
||||
uint16_t _dpi;
|
||||
|
|
|
@ -749,8 +749,12 @@ void OffscreenQmlSurface::resume() {
|
|||
_paused = false;
|
||||
_render = true;
|
||||
|
||||
getRootItem()->setProperty("eventBridge", QVariant::fromValue(this));
|
||||
getRootContext()->setContextProperty("webEntity", this);
|
||||
if (getRootItem()) {
|
||||
getRootItem()->setProperty("eventBridge", QVariant::fromValue(this));
|
||||
}
|
||||
if (getRootContext()) {
|
||||
getRootContext()->setContextProperty("webEntity", this);
|
||||
}
|
||||
}
|
||||
|
||||
bool OffscreenQmlSurface::isPaused() const {
|
||||
|
|
|
@ -92,6 +92,7 @@ void SparseInfo::maybeMakeSparse() {
|
|||
if (!texture._gpuObject.isAutogenerateMips()) {
|
||||
return;
|
||||
}
|
||||
return;
|
||||
|
||||
const uvec3 dimensions = texture._gpuObject.getDimensions();
|
||||
auto allowedPageDimensions = getPageDimensionsForFormat(texture._target, texture._internalFormat);
|
||||
|
|
|
@ -1084,17 +1084,17 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElementPointer element,
|
|||
params.stopReason = EncodeBitstreamParams::WAS_IN_VIEW;
|
||||
return bytesAtThisLevel;
|
||||
}
|
||||
}
|
||||
|
||||
// If we're not in delta sending mode, and we weren't asked to do a force send, and the voxel hasn't changed,
|
||||
// then we can also bail early and save bits
|
||||
if (!params.forceSendScene && !params.deltaView &&
|
||||
!element->hasChangedSince(params.lastQuerySent - CHANGE_FUDGE)) {
|
||||
if (params.stats) {
|
||||
params.stats->skippedNoChange(element);
|
||||
}
|
||||
params.stopReason = EncodeBitstreamParams::NO_CHANGE;
|
||||
return bytesAtThisLevel;
|
||||
// If we're not in delta sending mode, and we weren't asked to do a force send, and the octree element hasn't changed,
|
||||
// then we can also bail early and save bits
|
||||
if (!params.forceSendScene && !params.deltaView &&
|
||||
!element->hasChangedSince(params.lastQuerySent - CHANGE_FUDGE)) {
|
||||
if (params.stats) {
|
||||
params.stats->skippedNoChange(element);
|
||||
}
|
||||
params.stopReason = EncodeBitstreamParams::NO_CHANGE;
|
||||
return bytesAtThisLevel;
|
||||
}
|
||||
|
||||
bool keepDiggingDeeper = true; // Assuming we're in view we have a great work ethic, we're always ready for more!
|
||||
|
|
|
@ -13,15 +13,25 @@
|
|||
|
||||
void ContactInfo::update(uint32_t currentStep, const btManifoldPoint& p) {
|
||||
_lastStep = currentStep;
|
||||
++_numSteps;
|
||||
positionWorldOnB = p.m_positionWorldOnB;
|
||||
normalWorldOnB = p.m_normalWorldOnB;
|
||||
distance = p.m_distance1;
|
||||
}
|
||||
}
|
||||
|
||||
const uint32_t STEPS_BETWEEN_CONTINUE_EVENTS = 9;
|
||||
|
||||
ContactEventType ContactInfo::computeType(uint32_t thisStep) {
|
||||
if (_lastStep != thisStep) {
|
||||
return CONTACT_EVENT_TYPE_END;
|
||||
if (_continueExpiry == 0) {
|
||||
_continueExpiry = thisStep + STEPS_BETWEEN_CONTINUE_EVENTS;
|
||||
return CONTACT_EVENT_TYPE_START;
|
||||
}
|
||||
return (_numSteps == 1) ? CONTACT_EVENT_TYPE_START : CONTACT_EVENT_TYPE_CONTINUE;
|
||||
return (_lastStep == thisStep) ? CONTACT_EVENT_TYPE_CONTINUE : CONTACT_EVENT_TYPE_END;
|
||||
}
|
||||
|
||||
bool ContactInfo::readyForContinue(uint32_t thisStep) {
|
||||
if (thisStep > _continueExpiry) {
|
||||
_continueExpiry = thisStep + STEPS_BETWEEN_CONTINUE_EVENTS;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -19,20 +19,22 @@
|
|||
|
||||
|
||||
class ContactInfo {
|
||||
public:
|
||||
public:
|
||||
void update(uint32_t currentStep, const btManifoldPoint& p);
|
||||
ContactEventType computeType(uint32_t thisStep);
|
||||
|
||||
const btVector3& getPositionWorldOnB() const { return positionWorldOnB; }
|
||||
btVector3 getPositionWorldOnA() const { return positionWorldOnB + normalWorldOnB * distance; }
|
||||
|
||||
bool readyForContinue(uint32_t thisStep);
|
||||
|
||||
btVector3 positionWorldOnB;
|
||||
btVector3 normalWorldOnB;
|
||||
btScalar distance;
|
||||
private:
|
||||
uint32_t _lastStep = 0;
|
||||
uint32_t _numSteps = 0;
|
||||
};
|
||||
uint32_t _lastStep { 0 };
|
||||
uint32_t _continueExpiry { 0 };
|
||||
};
|
||||
|
||||
|
||||
#endif // hifi_ContactEvent_h
|
||||
|
|
|
@ -762,6 +762,11 @@ void EntityMotionState::computeCollisionGroupAndMask(int16_t& group, int16_t& ma
|
|||
_entity->computeCollisionGroupAndFinalMask(group, mask);
|
||||
}
|
||||
|
||||
bool EntityMotionState::shouldBeLocallyOwned() const {
|
||||
return (_outgoingPriority > VOLUNTEER_SIMULATION_PRIORITY && _outgoingPriority > _entity->getSimulationPriority()) ||
|
||||
_entity->getSimulatorID() == Physics::getSessionUUID();
|
||||
}
|
||||
|
||||
void EntityMotionState::upgradeOutgoingPriority(uint8_t priority) {
|
||||
_outgoingPriority = glm::max<uint8_t>(_outgoingPriority, priority);
|
||||
}
|
||||
|
|
|
@ -78,6 +78,8 @@ public:
|
|||
|
||||
virtual void computeCollisionGroupAndMask(int16_t& group, int16_t& mask) const override;
|
||||
|
||||
bool shouldBeLocallyOwned() const override;
|
||||
|
||||
friend class PhysicalEntitySimulation;
|
||||
|
||||
protected:
|
||||
|
|
|
@ -133,6 +133,7 @@ QVariantMap ObjectAction::getArguments() {
|
|||
arguments["::no-motion-state"] = true;
|
||||
}
|
||||
}
|
||||
arguments["isMine"] = isMine();
|
||||
});
|
||||
return arguments;
|
||||
}
|
||||
|
|
|
@ -146,6 +146,8 @@ public:
|
|||
void dirtyInternalKinematicChanges() { _hasInternalKinematicChanges = true; }
|
||||
void clearInternalKinematicChanges() { _hasInternalKinematicChanges = false; }
|
||||
|
||||
virtual bool shouldBeLocallyOwned() const { return false; }
|
||||
|
||||
friend class PhysicsEngine;
|
||||
|
||||
protected:
|
||||
|
|
|
@ -270,7 +270,7 @@ void PhysicsEngine::stepSimulation() {
|
|||
}
|
||||
|
||||
auto onSubStep = [this]() {
|
||||
updateContactMap();
|
||||
this->updateContactMap();
|
||||
};
|
||||
|
||||
int numSubsteps = _dynamicsWorld->stepSimulationWithSubstepCallback(timeStep, PHYSICS_ENGINE_MAX_NUM_SUBSTEPS,
|
||||
|
@ -393,7 +393,6 @@ void PhysicsEngine::updateContactMap() {
|
|||
}
|
||||
|
||||
const CollisionEvents& PhysicsEngine::getCollisionEvents() {
|
||||
const uint32_t CONTINUE_EVENT_FILTER_FREQUENCY = 10;
|
||||
_collisionEvents.clear();
|
||||
|
||||
// scan known contacts and trigger events
|
||||
|
@ -402,28 +401,42 @@ const CollisionEvents& PhysicsEngine::getCollisionEvents() {
|
|||
while (contactItr != _contactMap.end()) {
|
||||
ContactInfo& contact = contactItr->second;
|
||||
ContactEventType type = contact.computeType(_numContactFrames);
|
||||
if(type != CONTACT_EVENT_TYPE_CONTINUE || _numSubsteps % CONTINUE_EVENT_FILTER_FREQUENCY == 0) {
|
||||
const btScalar SIGNIFICANT_DEPTH = -0.002f; // penetrations have negative distance
|
||||
if (type != CONTACT_EVENT_TYPE_CONTINUE ||
|
||||
(contact.distance < SIGNIFICANT_DEPTH &&
|
||||
contact.readyForContinue(_numContactFrames))) {
|
||||
ObjectMotionState* motionStateA = static_cast<ObjectMotionState*>(contactItr->first._a);
|
||||
ObjectMotionState* motionStateB = static_cast<ObjectMotionState*>(contactItr->first._b);
|
||||
glm::vec3 velocityChange = (motionStateA ? motionStateA->getObjectLinearVelocityChange() : glm::vec3(0.0f)) +
|
||||
(motionStateB ? motionStateB->getObjectLinearVelocityChange() : glm::vec3(0.0f));
|
||||
|
||||
if (motionStateA) {
|
||||
// NOTE: the MyAvatar RigidBody is the only object in the simulation that does NOT have a MotionState
|
||||
// which means should we ever want to report ALL collision events against the avatar we can
|
||||
// modify the logic below.
|
||||
//
|
||||
// We only create events when at least one of the objects is (or should be) owned in the local simulation.
|
||||
if (motionStateA && (motionStateA->shouldBeLocallyOwned())) {
|
||||
QUuid idA = motionStateA->getObjectID();
|
||||
QUuid idB;
|
||||
if (motionStateB) {
|
||||
idB = motionStateB->getObjectID();
|
||||
}
|
||||
glm::vec3 position = bulletToGLM(contact.getPositionWorldOnB()) + _originOffset;
|
||||
glm::vec3 velocityChange = motionStateA->getObjectLinearVelocityChange() +
|
||||
(motionStateB ? motionStateB->getObjectLinearVelocityChange() : glm::vec3(0.0f));
|
||||
glm::vec3 penetration = bulletToGLM(contact.distance * contact.normalWorldOnB);
|
||||
_collisionEvents.push_back(Collision(type, idA, idB, position, penetration, velocityChange));
|
||||
} else if (motionStateB) {
|
||||
} else if (motionStateB && (motionStateB->shouldBeLocallyOwned())) {
|
||||
QUuid idB = motionStateB->getObjectID();
|
||||
QUuid idA;
|
||||
if (motionStateA) {
|
||||
idA = motionStateA->getObjectID();
|
||||
}
|
||||
glm::vec3 position = bulletToGLM(contact.getPositionWorldOnA()) + _originOffset;
|
||||
glm::vec3 velocityChange = motionStateB->getObjectLinearVelocityChange() +
|
||||
(motionStateA ? motionStateA->getObjectLinearVelocityChange() : glm::vec3(0.0f));
|
||||
// NOTE: we're flipping the order of A and B (so that the first objectID is never NULL)
|
||||
// hence we must negate the penetration.
|
||||
// hence we negate the penetration (because penetration always points from B to A).
|
||||
glm::vec3 penetration = - bulletToGLM(contact.distance * contact.normalWorldOnB);
|
||||
_collisionEvents.push_back(Collision(type, idB, QUuid(), position, penetration, velocityChange));
|
||||
_collisionEvents.push_back(Collision(type, idB, idA, position, penetration, velocityChange));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -36,7 +36,9 @@
|
|||
#include "simple_textured_frag.h"
|
||||
#include "simple_textured_unlit_frag.h"
|
||||
#include "simple_opaque_web_browser_frag.h"
|
||||
#include "simple_opaque_web_browser_overlay_frag.h"
|
||||
#include "simple_transparent_web_browser_frag.h"
|
||||
#include "simple_transparent_web_browser_overlay_frag.h"
|
||||
#include "glowLine_vert.h"
|
||||
#include "glowLine_frag.h"
|
||||
|
||||
|
@ -1760,66 +1762,61 @@ inline bool operator==(const SimpleProgramKey& a, const SimpleProgramKey& b) {
|
|||
return a.getRaw() == b.getRaw();
|
||||
}
|
||||
|
||||
void GeometryCache::bindOpaqueWebBrowserProgram(gpu::Batch& batch) {
|
||||
batch.setPipeline(getOpaqueWebBrowserProgram());
|
||||
static void buildWebShader(const std::string& vertShaderText, const std::string& fragShaderText, bool blendEnable,
|
||||
gpu::ShaderPointer& shaderPointerOut, gpu::PipelinePointer& pipelinePointerOut) {
|
||||
auto VS = gpu::Shader::createVertex(vertShaderText);
|
||||
auto PS = gpu::Shader::createPixel(fragShaderText);
|
||||
|
||||
shaderPointerOut = gpu::Shader::createProgram(VS, PS);
|
||||
|
||||
gpu::Shader::BindingSet slotBindings;
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("normalFittingMap"), render::ShapePipeline::Slot::MAP::NORMAL_FITTING));
|
||||
gpu::Shader::makeProgram(*shaderPointerOut, slotBindings);
|
||||
auto state = std::make_shared<gpu::State>();
|
||||
state->setCullMode(gpu::State::CULL_NONE);
|
||||
state->setDepthTest(true, true, gpu::LESS_EQUAL);
|
||||
state->setBlendFunction(blendEnable,
|
||||
gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA,
|
||||
gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
|
||||
|
||||
pipelinePointerOut = gpu::Pipeline::create(shaderPointerOut, state);
|
||||
}
|
||||
|
||||
void GeometryCache::bindOpaqueWebBrowserProgram(gpu::Batch& batch, bool isAA) {
|
||||
batch.setPipeline(getOpaqueWebBrowserProgram(isAA));
|
||||
// Set a default normal map
|
||||
batch.setResourceTexture(render::ShapePipeline::Slot::MAP::NORMAL_FITTING,
|
||||
DependencyManager::get<TextureCache>()->getNormalFittingTexture());
|
||||
}
|
||||
|
||||
gpu::PipelinePointer GeometryCache::getOpaqueWebBrowserProgram() {
|
||||
gpu::PipelinePointer GeometryCache::getOpaqueWebBrowserProgram(bool isAA) {
|
||||
static std::once_flag once;
|
||||
std::call_once(once, [&]() {
|
||||
auto VS = gpu::Shader::createVertex(std::string(simple_vert));
|
||||
auto PS = gpu::Shader::createPixel(std::string(simple_opaque_web_browser_frag));
|
||||
|
||||
_simpleOpaqueWebBrowserShader = gpu::Shader::createProgram(VS, PS);
|
||||
|
||||
gpu::Shader::BindingSet slotBindings;
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("normalFittingMap"), render::ShapePipeline::Slot::MAP::NORMAL_FITTING));
|
||||
gpu::Shader::makeProgram(*_simpleOpaqueWebBrowserShader, slotBindings);
|
||||
auto state = std::make_shared<gpu::State>();
|
||||
state->setCullMode(gpu::State::CULL_NONE);
|
||||
state->setDepthTest(true, true, gpu::LESS_EQUAL);
|
||||
state->setBlendFunction(false,
|
||||
gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA,
|
||||
gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
|
||||
|
||||
_simpleOpaqueWebBrowserPipeline = gpu::Pipeline::create(_simpleOpaqueWebBrowserShader, state);
|
||||
const bool BLEND_ENABLE = false;
|
||||
buildWebShader(simple_vert, simple_opaque_web_browser_frag, BLEND_ENABLE, _simpleOpaqueWebBrowserShader, _simpleOpaqueWebBrowserPipeline);
|
||||
buildWebShader(simple_vert, simple_opaque_web_browser_overlay_frag, BLEND_ENABLE, _simpleOpaqueWebBrowserOverlayShader, _simpleOpaqueWebBrowserOverlayPipeline);
|
||||
});
|
||||
|
||||
return _simpleOpaqueWebBrowserPipeline;
|
||||
return isAA ? _simpleOpaqueWebBrowserPipeline : _simpleOpaqueWebBrowserOverlayPipeline;
|
||||
}
|
||||
|
||||
void GeometryCache::bindTransparentWebBrowserProgram(gpu::Batch& batch) {
|
||||
batch.setPipeline(getTransparentWebBrowserProgram());
|
||||
void GeometryCache::bindTransparentWebBrowserProgram(gpu::Batch& batch, bool isAA) {
|
||||
batch.setPipeline(getTransparentWebBrowserProgram(isAA));
|
||||
// Set a default normal map
|
||||
batch.setResourceTexture(render::ShapePipeline::Slot::MAP::NORMAL_FITTING,
|
||||
DependencyManager::get<TextureCache>()->getNormalFittingTexture());
|
||||
}
|
||||
|
||||
gpu::PipelinePointer GeometryCache::getTransparentWebBrowserProgram() {
|
||||
gpu::PipelinePointer GeometryCache::getTransparentWebBrowserProgram(bool isAA) {
|
||||
static std::once_flag once;
|
||||
std::call_once(once, [&]() {
|
||||
auto VS = gpu::Shader::createVertex(std::string(simple_vert));
|
||||
auto PS = gpu::Shader::createPixel(std::string(simple_transparent_web_browser_frag));
|
||||
|
||||
_simpleTransparentWebBrowserShader = gpu::Shader::createProgram(VS, PS);
|
||||
|
||||
gpu::Shader::BindingSet slotBindings;
|
||||
slotBindings.insert(gpu::Shader::Binding(std::string("normalFittingMap"), render::ShapePipeline::Slot::MAP::NORMAL_FITTING));
|
||||
gpu::Shader::makeProgram(*_simpleTransparentWebBrowserShader, slotBindings);
|
||||
auto state = std::make_shared<gpu::State>();
|
||||
state->setCullMode(gpu::State::CULL_NONE);
|
||||
state->setDepthTest(true, true, gpu::LESS_EQUAL);
|
||||
state->setBlendFunction(true,
|
||||
gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA,
|
||||
gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
|
||||
|
||||
_simpleTransparentWebBrowserPipeline = gpu::Pipeline::create(_simpleTransparentWebBrowserShader, state);
|
||||
const bool BLEND_ENABLE = true;
|
||||
buildWebShader(simple_vert, simple_transparent_web_browser_frag, BLEND_ENABLE, _simpleTransparentWebBrowserShader, _simpleTransparentWebBrowserPipeline);
|
||||
buildWebShader(simple_vert, simple_transparent_web_browser_overlay_frag, BLEND_ENABLE, _simpleTransparentWebBrowserOverlayShader, _simpleTransparentWebBrowserOverlayPipeline);
|
||||
});
|
||||
|
||||
return _simpleTransparentWebBrowserPipeline;
|
||||
return isAA ? _simpleTransparentWebBrowserPipeline : _simpleTransparentWebBrowserOverlayPipeline;
|
||||
}
|
||||
|
||||
void GeometryCache::bindSimpleProgram(gpu::Batch& batch, bool textured, bool transparent, bool culled, bool unlit, bool depthBiased) {
|
||||
|
|
|
@ -158,11 +158,11 @@ public:
|
|||
gpu::PipelinePointer getSimplePipeline(bool textured = false, bool transparent = false, bool culled = true,
|
||||
bool unlit = false, bool depthBias = false);
|
||||
|
||||
void bindOpaqueWebBrowserProgram(gpu::Batch& batch);
|
||||
gpu::PipelinePointer getOpaqueWebBrowserProgram();
|
||||
void bindOpaqueWebBrowserProgram(gpu::Batch& batch, bool isAA);
|
||||
gpu::PipelinePointer getOpaqueWebBrowserProgram(bool isAA);
|
||||
|
||||
void bindTransparentWebBrowserProgram(gpu::Batch& batch);
|
||||
gpu::PipelinePointer getTransparentWebBrowserProgram();
|
||||
void bindTransparentWebBrowserProgram(gpu::Batch& batch, bool isAA);
|
||||
gpu::PipelinePointer getTransparentWebBrowserProgram(bool isAA);
|
||||
|
||||
render::ShapePipelinePointer getOpaqueShapePipeline() { return GeometryCache::_simpleOpaquePipeline; }
|
||||
render::ShapePipelinePointer getTransparentShapePipeline() { return GeometryCache::_simpleTransparentPipeline; }
|
||||
|
@ -420,15 +420,21 @@ private:
|
|||
gpu::ShaderPointer _unlitShader;
|
||||
static render::ShapePipelinePointer _simpleOpaquePipeline;
|
||||
static render::ShapePipelinePointer _simpleTransparentPipeline;
|
||||
static render::ShapePipelinePointer _simpleOpaqueOverlayPipeline;
|
||||
static render::ShapePipelinePointer _simpleTransparentOverlayPipeline;
|
||||
static render::ShapePipelinePointer _simpleWirePipeline;
|
||||
gpu::PipelinePointer _glowLinePipeline;
|
||||
QHash<SimpleProgramKey, gpu::PipelinePointer> _simplePrograms;
|
||||
|
||||
gpu::ShaderPointer _simpleOpaqueWebBrowserShader;
|
||||
gpu::PipelinePointer _simpleOpaqueWebBrowserPipeline;
|
||||
|
||||
gpu::ShaderPointer _simpleTransparentWebBrowserShader;
|
||||
gpu::PipelinePointer _simpleTransparentWebBrowserPipeline;
|
||||
|
||||
gpu::ShaderPointer _simpleOpaqueWebBrowserOverlayShader;
|
||||
gpu::PipelinePointer _simpleOpaqueWebBrowserOverlayPipeline;
|
||||
gpu::ShaderPointer _simpleTransparentWebBrowserOverlayShader;
|
||||
gpu::PipelinePointer _simpleTransparentWebBrowserOverlayPipeline;
|
||||
};
|
||||
|
||||
#endif // hifi_GeometryCache_h
|
||||
|
|
|
@ -92,6 +92,15 @@ bool LightingModel::isAlbedoEnabled() const {
|
|||
return (bool)_parametersBuffer.get<Parameters>().enableAlbedo;
|
||||
}
|
||||
|
||||
void LightingModel::setMaterialTexturing(bool enable) {
|
||||
if (enable != isMaterialTexturingEnabled()) {
|
||||
_parametersBuffer.edit<Parameters>().enableMaterialTexturing = (float)enable;
|
||||
}
|
||||
}
|
||||
bool LightingModel::isMaterialTexturingEnabled() const {
|
||||
return (bool)_parametersBuffer.get<Parameters>().enableMaterialTexturing;
|
||||
}
|
||||
|
||||
void LightingModel::setAmbientLight(bool enable) {
|
||||
if (enable != isAmbientLightEnabled()) {
|
||||
_parametersBuffer.edit<Parameters>().enableAmbientLight = (float)enable;
|
||||
|
@ -150,6 +159,8 @@ void MakeLightingModel::configure(const Config& config) {
|
|||
_lightingModel->setSpecular(config.enableSpecular);
|
||||
_lightingModel->setAlbedo(config.enableAlbedo);
|
||||
|
||||
_lightingModel->setMaterialTexturing(config.enableMaterialTexturing);
|
||||
|
||||
_lightingModel->setAmbientLight(config.enableAmbientLight);
|
||||
_lightingModel->setDirectionalLight(config.enableDirectionalLight);
|
||||
_lightingModel->setPointLight(config.enablePointLight);
|
||||
|
@ -160,5 +171,8 @@ void MakeLightingModel::configure(const Config& config) {
|
|||
|
||||
void MakeLightingModel::run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, LightingModelPointer& lightingModel) {
|
||||
|
||||
lightingModel = _lightingModel;
|
||||
lightingModel = _lightingModel;
|
||||
|
||||
// make sure the enableTexturing flag of the render ARgs is in sync
|
||||
renderContext->args->_enableTexturing = _lightingModel->isMaterialTexturingEnabled();
|
||||
}
|