Merge remote-tracking branch 'upstream/tablet-ui' into 21209

# Conflicts:
#	interface/src/ui/overlays/Web3DOverlay.cpp
This commit is contained in:
David Rowe 2017-03-11 09:14:06 +13:00
commit 71713b10f3
45 changed files with 1511 additions and 1379 deletions

View file

@ -241,6 +241,7 @@ void AudioMixer::sendStatsPacket() {
statsObject["avg_streams_per_frame"] = (float)_stats.sumStreams / (float)_numStatFrames;
statsObject["avg_listeners_per_frame"] = (float)_stats.sumListeners / (float)_numStatFrames;
statsObject["avg_listeners_(silent)_per_frame"] = (float)_stats.sumListenersSilent / (float)_numStatFrames;
statsObject["silent_packets_per_frame"] = (float)_numSilentPackets / (float)_numStatFrames;

View file

@ -106,6 +106,7 @@ void AudioMixerSlave::mix(const SharedNodePointer& node) {
sendMixPacket(node, *data, encodedBuffer);
} else {
++stats.sumListenersSilent;
sendSilentPacket(node, *data);
}
@ -221,17 +222,19 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& listener) {
stats.mixTime += mixTime.count();
#endif
// use the per listener AudioLimiter to render the mixed data...
listenerData->audioLimiter.render(_mixSamples, _bufferSamples, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL);
// check for silent audio after the peak limiter has converted the samples
// check for silent audio before limiting
// limiting uses a dither and can only guarantee abs(sample) <= 1
bool hasAudio = false;
for (int i = 0; i < AudioConstants::NETWORK_FRAME_SAMPLES_STEREO; ++i) {
if (_bufferSamples[i] != 0) {
if (_mixSamples[i] != 0.0f) {
hasAudio = true;
break;
}
}
// use the per listener AudioLimiter to render the mixed data
listenerData->audioLimiter.render(_mixSamples, _bufferSamples, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL);
return hasAudio;
}

View file

@ -14,6 +14,7 @@
void AudioMixerStats::reset() {
sumStreams = 0;
sumListeners = 0;
sumListenersSilent = 0;
totalMixes = 0;
hrtfRenders = 0;
hrtfSilentRenders = 0;
@ -28,6 +29,7 @@ void AudioMixerStats::reset() {
void AudioMixerStats::accumulate(const AudioMixerStats& otherStats) {
sumStreams += otherStats.sumStreams;
sumListeners += otherStats.sumListeners;
sumListenersSilent += otherStats.sumListenersSilent;
totalMixes += otherStats.totalMixes;
hrtfRenders += otherStats.hrtfRenders;
hrtfSilentRenders += otherStats.hrtfSilentRenders;

View file

@ -19,6 +19,7 @@
struct AudioMixerStats {
int sumStreams { 0 };
int sumListeners { 0 };
int sumListenersSilent { 0 };
int totalMixes { 0 };

View file

@ -0,0 +1,339 @@
//
// TabletCustomQueryDialog.qml
//
// Created by Vlad Stelmahovsky on 3/27/17
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Dialogs 1.2 as OriginalDialogs
import "../controls-uit" as ControlsUIT
import "../styles-uit"
import "../windows"
TabletModalWindow {
id: root;
HifiConstants { id: hifi; }
y: hifi.dimensions.tabletMenuHeader
title: ""
visible: true;
property bool keyboardOverride: true
signal selected(var result);
signal canceled();
property int icon: hifi.icons.none;
property string iconText: "";
property int iconSize: 35;
onIconChanged: updateIcon();
property var textInput;
property var comboBox;
property var checkBox;
onTextInputChanged: {
if (textInput && textInput.text !== undefined) {
textField.text = textInput.text;
}
}
onComboBoxChanged: {
if (comboBox && comboBox.index !== undefined) {
comboBoxField.currentIndex = comboBox.index;
}
}
onCheckBoxChanged: {
if (checkBox && checkBox.checked !== undefined) {
checkBoxField.checked = checkBox.checked;
}
}
property bool keyboardEnabled: false
property bool keyboardRaised: false
property bool punctuationMode: false
onKeyboardRaisedChanged: d.resize();
property var warning: "";
property var result;
property var implicitCheckState: null;
property int titleWidth: 0;
onTitleWidthChanged: d.resize();
MouseArea {
width: parent.width
height: parent.height
}
function updateIcon() {
if (!root) {
return;
}
iconText = hifi.glyphForIcon(root.icon);
}
function updateCheckbox() {
if (checkBox.disableForItems) {
var currentItemInDisableList = false;
for (var i in checkBox.disableForItems) {
if (comboBoxField.currentIndex === checkBox.disableForItems[i]) {
currentItemInDisableList = true;
break;
}
}
if (currentItemInDisableList) {
checkBoxField.enabled = false;
if (checkBox.checkStateOnDisable !== null && checkBox.checkStateOnDisable !== undefined) {
root.implicitCheckState = checkBoxField.checked;
checkBoxField.checked = checkBox.checkStateOnDisable;
}
root.warning = checkBox.warningOnDisable;
} else {
checkBoxField.enabled = true;
if (root.implicitCheckState !== null) {
checkBoxField.checked = root.implicitCheckState;
root.implicitCheckState = null;
}
root.warning = "";
}
}
}
TabletModalFrame {
id: modalWindowItem
width: parent.width - 12
height: 240
anchors {
verticalCenter: parent.verticalCenter
horizontalCenter: parent.horizontalCenter
}
QtObject {
id: d;
readonly property int minWidth: 470
readonly property int maxWidth: 470
readonly property int minHeight: 240
readonly property int maxHeight: 720
function resize() {
var targetWidth = Math.max(titleWidth, 470);
var targetHeight = (textField.visible ? textField.controlHeight + hifi.dimensions.contentSpacing.y : 0) +
(extraInputs.visible ? extraInputs.height + hifi.dimensions.contentSpacing.y : 0) +
(buttons.height + 3 * hifi.dimensions.contentSpacing.y) +
((keyboardEnabled && keyboardRaised) ? (keyboard.raisedHeight + hifi.dimensions.contentSpacing.y) : 0);
root.width = (targetWidth < d.minWidth) ? d.minWidth : ((targetWidth > d.maxWdith) ? d.maxWidth : targetWidth);
root.height = (targetHeight < d.minHeight) ? d.minHeight : ((targetHeight > d.maxHeight) ?
d.maxHeight : targetHeight);
if (checkBoxField.visible && comboBoxField.visible) {
checkBoxField.width = extraInputs.width / 2;
comboBoxField.width = extraInputs.width / 2;
} else if (!checkBoxField.visible && comboBoxField.visible) {
comboBoxField.width = extraInputs.width;
}
}
}
// Item {
// anchors {
// // top: parent.top;
// // bottom: extraInputs.visible ? extraInputs.top : buttons.top;
// left: parent.left;
// right: parent.right;
// topMargin: 20
// }
// FIXME make a text field type that can be bound to a history for autocompletion
ControlsUIT.TextField {
id: textField;
label: root.textInput.label;
focus: root.textInput ? true : false;
visible: root.textInput ? true : false;
anchors {
top: parent.top
left: parent.left;
right: parent.right;
leftMargin: 5; rightMargin: 5;
topMargin: textField.controlHeight - textField.height + 5
}
}
ControlsUIT.Keyboard {
id: keyboard
raised: keyboardEnabled && keyboardRaised
numeric: punctuationMode
anchors {
left: parent.left
right: parent.right
leftMargin: 5; rightMargin: 5;
top: textField.bottom
topMargin: raised ? hifi.dimensions.contentSpacing.y : 0
}
}
// }
Row {
id: extraInputs;
visible: Boolean(root.checkBox || root.comboBox);
anchors {
left: parent.left;
right: parent.right;
leftMargin: 5; rightMargin: 5;
top: keyboard.bottom;
topMargin: hifi.dimensions.contentSpacing.y + 20;
}
height: comboBoxField.controlHeight;
onHeightChanged: d.resize();
onWidthChanged: d.resize();
ControlsUIT.CheckBox {
id: checkBoxField;
text: root.checkBox.label;
focus: Boolean(root.checkBox);
visible: Boolean(root.checkBox);
// anchors {
// left: parent.left;
// bottom: parent.bottom;
// leftMargin: 6; // Magic number to align with warning icon
// bottomMargin: 6;
// }
}
ControlsUIT.TabletComboBox {
id: comboBoxField;
label: root.comboBox.label;
focus: Boolean(root.comboBox);
visible: Boolean(root.comboBox);
// anchors {
// right: parent.right;
// bottom: parent.bottom;
// }
model: root.comboBox ? root.comboBox.items : [];
onAccepted: {
updateCheckbox();
focus = true;
}
}
}
Row {
id: buttons;
focus: true;
spacing: hifi.dimensions.contentSpacing.x;
layoutDirection: Qt.RightToLeft;
onHeightChanged: d.resize();
onWidthChanged: {
d.resize();
resizeWarningText();
}
anchors {
bottom: parent.bottom;
left: parent.left;
right: parent.right;
bottomMargin: hifi.dimensions.contentSpacing.y;
leftMargin: 5; rightMargin: 5;
}
function resizeWarningText() {
var rowWidth = buttons.width;
var buttonsWidth = acceptButton.width + cancelButton.width + hifi.dimensions.contentSpacing.x * 2;
var warningIconWidth = warningIcon.width + hifi.dimensions.contentSpacing.x;
warningText.width = rowWidth - buttonsWidth - warningIconWidth;
}
ControlsUIT.Button {
id: cancelButton;
action: cancelAction;
}
ControlsUIT.Button {
id: acceptButton;
action: acceptAction;
}
Text {
id: warningText;
visible: Boolean(root.warning);
text: root.warning;
wrapMode: Text.WordWrap;
font.italic: true;
maximumLineCount: 2;
}
HiFiGlyphs {
id: warningIcon;
visible: Boolean(root.warning);
text: hifi.glyphs.alert;
size: hifi.dimensions.controlLineHeight;
}
}
// }//column
Action {
id: cancelAction;
text: qsTr("Cancel");
shortcut: Qt.Key_Escape;
onTriggered: {
root.result = null;
root.canceled();
root.destroy();
}
}
Action {
id: acceptAction;
text: qsTr("Add");
shortcut: Qt.Key_Return;
onTriggered: {
var result = {};
if (textInput) {
result.textInput = textField.text;
}
if (comboBox) {
result.comboBox = comboBoxField.currentIndex;
result.comboBoxText = comboBoxField.currentText;
}
if (checkBox) {
result.checkBox = checkBoxField.enabled ? checkBoxField.checked : null;
}
root.result = JSON.stringify(result);
root.selected(root.result);
root.destroy();
}
}
}
Keys.onPressed: {
if (!visible) {
return;
}
switch (event.key) {
case Qt.Key_Escape:
case Qt.Key_Back:
cancelAction.trigger();
event.accepted = true;
break;
case Qt.Key_Return:
case Qt.Key_Enter:
acceptAction.trigger();
event.accepted = true;
break;
}
}
Component.onCompleted: {
keyboardEnabled = HMD.active;
updateIcon();
updateCheckbox();
d.resize();
textField.forceActiveFocus();
}
}

View file

@ -66,8 +66,6 @@ TabletModalWindow {
signal canceled();
Component.onCompleted: {
console.log("Helper " + helper + " drives " + drives);
fileDialogItem.keyboardEnabled = HMD.active;
// HACK: The following lines force the model to initialize properly such that the go-up button
@ -151,11 +149,12 @@ TabletModalWindow {
}
}
/*TabletComboBox {
TabletComboBox {
id: pathSelector
anchors {
z: 10
anchors {
top: parent.top
topMargin: hifi.dimensions.contentMargin.y
topMargin: (fileDialogItem.hasTitle ? (fileDialogItem.frameMarginTop + hifi.dimensions.modalDialogMargin.y) : hifi.dimension.modalDialogMargin.y)
left: navControls.right
leftMargin: hifi.dimensions.contentSpacing.x
right: parent.right
@ -219,7 +218,7 @@ TabletModalWindow {
fileTableView.forceActiveFocus();
}
}
}*/
}
QtObject {
id: d
@ -458,6 +457,7 @@ TabletModalWindow {
bottomMargin: hifi.dimensions.contentSpacing.y + currentSelection.controlHeight - currentSelection.height
}
headerVisible: !selectDirectory
onClicked: navigateToRow(row);
onDoubleClicked: navigateToRow(row);
focus: true
Keys.onReturnPressed: navigateToCurrentRow();

View file

@ -23,7 +23,10 @@ Preference {
Component.onCompleted: {
dataTextField.text = preference.value;
console.log("MyAvatar modelName " + MyAvatar.getFullAvatarModelName())
// FIXME: MyAvatar object isn't available in HMD mode for some reason.
if (typeof MyAvatar !== "undefined") {
console.log("MyAvatar modelName " + MyAvatar.getFullAvatarModelName())
}
console.log("Application : " + ApplicationInterface)
ApplicationInterface.fullAvatarURLChanged.connect(processNewAvatar);
}

View file

@ -1,5 +1,5 @@
//
// PreferencesDialog.qml
// GeneralPreferencesDialog.qml
//
// Created by Bradley Austin Davis on 24 Jan 2016
// Copyright 2015 High Fidelity, Inc.

View file

@ -0,0 +1,614 @@
//
// TabletAssetServer.qml
//
// Created by Vlad Stelmahovsky on 3/3/17
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Dialogs 1.2 as OriginalDialogs
import Qt.labs.settings 1.0
import "../../styles-uit"
import "../../controls-uit" as HifiControls
import "../../windows"
Rectangle {
id: root
objectName: "AssetServer"
property var eventBridge;
signal sendToScript(var message);
property bool isHMD: false
color: hifi.colors.baseGray
property int colorScheme: hifi.colorSchemes.dark
HifiConstants { id: hifi }
property var scripts: ScriptDiscoveryService;
property var assetProxyModel: Assets.proxyModel;
property var assetMappingsModel: Assets.mappingModel;
property var currentDirectory;
Settings {
category: "Overlay.AssetServer"
property alias directory: root.currentDirectory
}
Component.onCompleted: {
isHMD = HMD.active;
ApplicationInterface.uploadRequest.connect(uploadClicked);
assetMappingsModel.errorGettingMappings.connect(handleGetMappingsError);
reload();
}
function doDeleteFile(path) {
console.log("Deleting " + path);
Assets.deleteMappings(path, function(err) {
if (err) {
console.log("Asset browser - error deleting path: ", path, err);
box = errorMessageBox("There was an error deleting:\n" + path + "\n" + err);
box.selected.connect(reload);
} else {
console.log("Asset browser - finished deleting path: ", path);
reload();
}
});
}
function doRenameFile(oldPath, newPath) {
if (newPath[0] !== "/") {
newPath = "/" + newPath;
}
if (oldPath[oldPath.length - 1] === '/' && newPath[newPath.length - 1] != '/') {
// this is a folder rename but the user neglected to add a trailing slash when providing a new path
newPath = newPath + "/";
}
if (Assets.isKnownFolder(newPath)) {
box = errorMessageBox("Cannot overwrite existing directory.");
box.selected.connect(reload);
}
console.log("Asset browser - renaming " + oldPath + " to " + newPath);
Assets.renameMapping(oldPath, newPath, function(err) {
if (err) {
console.log("Asset browser - error renaming: ", oldPath, "=>", newPath, " - error ", err);
box = errorMessageBox("There was an error renaming:\n" + oldPath + " to " + newPath + "\n" + err);
box.selected.connect(reload);
} else {
console.log("Asset browser - finished rename: ", oldPath, "=>", newPath);
}
reload();
});
}
function fileExists(path) {
return Assets.isKnownMapping(path);
}
function askForOverwrite(path, callback) {
var object = tabletRoot.messageBox({
icon: hifi.icons.question,
buttons: OriginalDialogs.StandardButton.Yes | OriginalDialogs.StandardButton.No,
defaultButton: OriginalDialogs.StandardButton.No,
title: "Overwrite File",
text: path + "\n" + "This file already exists. Do you want to overwrite it?"
});
object.selected.connect(function(button) {
if (button === OriginalDialogs.StandardButton.Yes) {
callback();
}
});
}
function canAddToWorld(path) {
var supportedExtensions = [/\.fbx\b/i, /\.obj\b/i];
return supportedExtensions.reduce(function(total, current) {
return total | new RegExp(current).test(path);
}, false);
}
function clear() {
Assets.mappingModel.clear();
}
function reload() {
Assets.mappingModel.refresh();
treeView.selection.clear();
}
function handleGetMappingsError(errorString) {
errorMessageBox(
"There was a problem retreiving the list of assets from your Asset Server.\n"
+ errorString
);
}
function addToWorld() {
var defaultURL = assetProxyModel.data(treeView.selection.currentIndex, 0x103);
if (!defaultURL || !canAddToWorld(defaultURL)) {
return;
}
var SHAPE_TYPE_NONE = 0;
var SHAPE_TYPE_SIMPLE_HULL = 1;
var SHAPE_TYPE_SIMPLE_COMPOUND = 2;
var SHAPE_TYPE_STATIC_MESH = 3;
var SHAPE_TYPES = [];
SHAPE_TYPES[SHAPE_TYPE_NONE] = "No Collision";
SHAPE_TYPES[SHAPE_TYPE_SIMPLE_HULL] = "Basic - Whole model";
SHAPE_TYPES[SHAPE_TYPE_SIMPLE_COMPOUND] = "Good - Sub-meshes";
SHAPE_TYPES[SHAPE_TYPE_STATIC_MESH] = "Exact - All polygons";
var SHAPE_TYPE_DEFAULT = SHAPE_TYPE_STATIC_MESH;
var DYNAMIC_DEFAULT = false;
var prompt = tabletRoot.customInputDialog({
textInput: {
label: "Model URL",
text: defaultURL
},
comboBox: {
label: "Automatic Collisions",
index: SHAPE_TYPE_DEFAULT,
items: SHAPE_TYPES
},
checkBox: {
label: "Dynamic",
checked: DYNAMIC_DEFAULT,
disableForItems: [
SHAPE_TYPE_STATIC_MESH
],
checkStateOnDisable: false,
warningOnDisable: "Models with automatic collisions set to 'Exact' cannot be dynamic"
}
});
prompt.selected.connect(function (jsonResult) {
if (jsonResult) {
var result = JSON.parse(jsonResult);
var url = result.textInput.trim();
var shapeType;
switch (result.comboBox) {
case SHAPE_TYPE_SIMPLE_HULL:
shapeType = "simple-hull";
break;
case SHAPE_TYPE_SIMPLE_COMPOUND:
shapeType = "simple-compound";
break;
case SHAPE_TYPE_STATIC_MESH:
shapeType = "static-mesh";
break;
default:
shapeType = "none";
}
var dynamic = result.checkBox !== null ? result.checkBox : DYNAMIC_DEFAULT;
if (shapeType === "static-mesh" && dynamic) {
// The prompt should prevent this case
print("Error: model cannot be both static mesh and dynamic. This should never happen.");
} else if (url) {
var name = assetProxyModel.data(treeView.selection.currentIndex);
var addPosition = Vec3.sum(MyAvatar.position, Vec3.multiply(2, Quat.getFront(MyAvatar.orientation)));
var gravity;
if (dynamic) {
// Create a vector <0, -10, 0>. { x: 0, y: -10, z: 0 } won't work because Qt is dumb and this is a
// different scripting engine from QTScript.
gravity = Vec3.multiply(Vec3.fromPolar(Math.PI / 2, 0), 10);
} else {
gravity = Vec3.multiply(Vec3.fromPolar(Math.PI / 2, 0), 0);
}
print("Asset browser - adding asset " + url + " (" + name + ") to world.");
// Entities.addEntity doesn't work from QML, so we use this.
Entities.addModelEntity(name, url, shapeType, dynamic, addPosition, gravity);
}
}
});
}
function copyURLToClipboard(index) {
if (!index) {
index = treeView.selection.currentIndex;
}
var url = assetProxyModel.data(treeView.selection.currentIndex, 0x103);
if (!url) {
return;
}
Window.copyToClipboard(url);
}
function renameEl(index, data) {
if (!index) {
return false;
}
var path = assetProxyModel.data(index, 0x100);
if (!path) {
return false;
}
var destinationPath = path.split('/');
destinationPath[destinationPath.length - (path[path.length - 1] === '/' ? 2 : 1)] = data;
destinationPath = destinationPath.join('/').trim();
if (path === destinationPath) {
return;
}
if (!fileExists(destinationPath)) {
doRenameFile(path, destinationPath);
}
}
function renameFile(index) {
if (!index) {
index = treeView.selection.currentIndex;
}
var path = assetProxyModel.data(index, 0x100);
if (!path) {
return;
}
var object = tabletRoot.inputDialog({
label: "Enter new path:",
current: path,
placeholderText: "Enter path here"
});
object.selected.connect(function(destinationPath) {
destinationPath = destinationPath.trim();
if (path === destinationPath) {
return;
}
if (fileExists(destinationPath)) {
askForOverwrite(destinationPath, function() {
doRenameFile(path, destinationPath);
});
} else {
doRenameFile(path, destinationPath);
}
});
}
function deleteFile(index) {
if (!index) {
index = treeView.selection.currentIndex;
}
var path = assetProxyModel.data(index, 0x100);
if (!path) {
return;
}
var isFolder = assetProxyModel.data(treeView.selection.currentIndex, 0x101);
var typeString = isFolder ? 'folder' : 'file';
var object = tabletRoot.messageBox({
icon: hifi.icons.question,
buttons: OriginalDialogs.StandardButton.Yes + OriginalDialogs.StandardButton.No,
defaultButton: OriginalDialogs.StandardButton.Yes,
title: "Delete",
text: "You are about to delete the following " + typeString + ":\n" + path + "\nDo you want to continue?"
});
object.selected.connect(function(button) {
if (button === OriginalDialogs.StandardButton.Yes) {
doDeleteFile(path);
}
});
}
Timer {
id: doUploadTimer
property var url
property bool isConnected: false
interval: 5
repeat: false
running: false
}
property bool uploadOpen: false;
Timer {
id: timer
}
function uploadClicked(fileUrl) {
if (uploadOpen) {
return;
}
uploadOpen = true;
function doUpload(url, dropping) {
var fileUrl = fileDialogHelper.urlToPath(url);
var path = assetProxyModel.data(treeView.selection.currentIndex, 0x100);
var directory = path ? path.slice(0, path.lastIndexOf('/') + 1) : "/";
var filename = fileUrl.slice(fileUrl.lastIndexOf('/') + 1);
Assets.uploadFile(fileUrl, directory + filename,
function() {
// Upload started
uploadSpinner.visible = true;
uploadButton.enabled = false;
uploadProgressLabel.text = "In progress...";
},
function(err, path) {
print(err, path);
if (err === "") {
uploadProgressLabel.text = "Upload Complete";
timer.interval = 1000;
timer.repeat = false;
timer.triggered.connect(function() {
uploadSpinner.visible = false;
uploadButton.enabled = true;
uploadOpen = false;
});
timer.start();
console.log("Asset Browser - finished uploading: ", fileUrl);
reload();
} else {
uploadSpinner.visible = false;
uploadButton.enabled = true;
uploadOpen = false;
if (err !== -1) {
console.log("Asset Browser - error uploading: ", fileUrl, " - error ", err);
var box = errorMessageBox("There was an error uploading:\n" + fileUrl + "\n" + err);
box.selected.connect(reload);
}
}
}, dropping);
}
function initiateUpload(url) {
doUpload(doUploadTimer.url, false);
}
if (fileUrl) {
doUpload(fileUrl, true);
} else {
var browser = tabletRoot.fileDialog({
selectDirectory: false,
dir: currentDirectory
});
browser.canceled.connect(function() {
uploadOpen = false;
});
browser.selectedFile.connect(function(url) {
currentDirectory = browser.dir;
// Initiate upload from a timer so that file browser dialog can close beforehand.
doUploadTimer.url = url;
if (!doUploadTimer.isConnected) {
doUploadTimer.triggered.connect(function() { initiateUpload(); });
doUploadTimer.isConnected = true;
}
doUploadTimer.start();
});
}
}
function errorMessageBox(message) {
return tabletRoot.messageBox({
icon: hifi.icons.warning,
defaultButton: OriginalDialogs.StandardButton.Ok,
title: "Error",
text: message
});
}
Column {
width: parent.width
y: hifi.dimensions.tabletMenuHeader //-bgNavBar
spacing: 10
HifiControls.TabletContentSection {
id: assetDirectory
name: "Asset Directory"
isFirst: true
HifiControls.VerticalSpacer {}
Row {
id: buttonRow
width: parent.width
height: 30
spacing: hifi.dimensions.contentSpacing.x
HifiControls.GlyphButton {
glyph: hifi.glyphs.reload
color: hifi.buttons.black
colorScheme: root.colorScheme
width: hifi.dimensions.controlLineHeight
onClicked: root.reload()
}
HifiControls.Button {
text: "Add To World"
color: hifi.buttons.black
colorScheme: root.colorScheme
width: 120
enabled: canAddToWorld(assetProxyModel.data(treeView.selection.currentIndex, 0x100))
onClicked: root.addToWorld()
}
HifiControls.Button {
text: "Rename"
color: hifi.buttons.black
colorScheme: root.colorScheme
width: 80
onClicked: root.renameFile()
enabled: treeView.selection.hasSelection
}
HifiControls.Button {
id: deleteButton
text: "Delete"
color: hifi.buttons.red
colorScheme: root.colorScheme
width: 80
onClicked: root.deleteFile()
enabled: treeView.selection.hasSelection
}
}
Menu {
id: contextMenu
title: "Edit"
property var url: ""
property var currentIndex: null
MenuItem {
text: "Copy URL"
onTriggered: {
copyURLToClipboard(contextMenu.currentIndex);
}
}
MenuItem {
text: "Rename"
onTriggered: {
renameFile(contextMenu.currentIndex);
}
}
MenuItem {
text: "Delete"
onTriggered: {
deleteFile(contextMenu.currentIndex);
}
}
}
}
HifiControls.Tree {
id: treeView
height: 430
anchors.leftMargin: hifi.dimensions.contentMargin.x + 2 // Extra for border
anchors.rightMargin: hifi.dimensions.contentMargin.x + 2 // Extra for border
anchors.left: parent.left
anchors.right: parent.right
treeModel: assetProxyModel
canEdit: true
colorScheme: root.colorScheme
modifyEl: renameEl
MouseArea {
propagateComposedEvents: true
anchors.fill: parent
acceptedButtons: Qt.RightButton
onClicked: {
if (!HMD.active) { // Popup only displays properly on desktop
var index = treeView.indexAt(mouse.x, mouse.y);
treeView.selection.setCurrentIndex(index, 0x0002);
contextMenu.currentIndex = index;
contextMenu.popup();
}
}
}
}
HifiControls.TabletContentSection {
id: uploadSection
name: "Upload A File"
spacing: hifi.dimensions.contentSpacing.y
//anchors.bottom: parent.bottom
height: 65
anchors.left: parent.left
anchors.right: parent.right
Item {
height: parent.height
width: parent.width
HifiControls.Button {
id: uploadButton
anchors.right: parent.right
text: "Choose File"
color: hifi.buttons.blue
colorScheme: root.colorScheme
height: 30
width: 155
onClicked: uploadClickedTimer.running = true
// For some reason trigginer an API that enters
// an internal event loop directly from the button clicked
// trigger below causes the appliction to behave oddly.
// Most likely because the button onClicked handling is never
// completed until the function returns.
// FIXME find a better way of handling the input dialogs that
// doesn't trigger this.
Timer {
id: uploadClickedTimer
interval: 5
repeat: false
running: false
onTriggered: uploadClicked();
}
}
Item {
id: uploadSpinner
visible: false
anchors.top: parent.top
anchors.left: parent.left
width: 40
height: 32
Image {
id: image
width: 24
height: 24
source: "../../../images/Loading-Outer-Ring.png"
RotationAnimation on rotation {
loops: Animation.Infinite
from: 0
to: 360
duration: 2000
}
}
Image {
width: 24
height: 24
source: "../../../images/Loading-Inner-H.png"
}
HifiControls.Label {
id: uploadProgressLabel
anchors.left: image.right
anchors.leftMargin: 10
anchors.verticalCenter: image.verticalCenter
text: "In progress..."
colorScheme: root.colorScheme
}
}
}
}
}
}

View file

@ -0,0 +1,42 @@
//
// TabletAudioPreferences.qml
//
// Created by Davd Rowe on 7 Mar 2017.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.5
import "tabletWindows"
import "../../dialogs"
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import QtGraphicalEffects 1.0
StackView {
id: profileRoot
initialItem: root
objectName: "stack"
property var eventBridge;
signal sendToScript(var message);
function pushSource(path) {
editRoot.push(Qt.reslovedUrl(path));
}
function popSource() {
}
TabletPreferencesDialog {
id: root
property string title: "Audio Settings"
objectName: "TabletAudioPreferences"
width: parent.width
height: parent.height
showCategories: ["Audio"]
}
}

View file

@ -0,0 +1,42 @@
//
// TabletAvatarPreferences.qml
//
// Created by Davd Rowe on 2 Mar 2017.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.5
import "tabletWindows"
import "../../dialogs"
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import QtGraphicalEffects 1.0
StackView {
id: profileRoot
initialItem: root
objectName: "stack"
property var eventBridge;
signal sendToScript(var message);
function pushSource(path) {
editRoot.push(Qt.reslovedUrl(path));
}
function popSource() {
}
TabletPreferencesDialog {
id: root
property string title: "Avatar Preferences"
objectName: "TabletAvatarPreferences"
width: parent.width
height: parent.height
showCategories: ["Avatar Basics", "Avatar Tuning", "Avatar Camera"]
}
}

View file

@ -1,9 +1,8 @@
//
// TabletGeneralSettings.qml
// scripts/system/
// TabletGeneralPreferences.qml
//
// Created by Dante Ruiz on 9 Feb 2017
// Copyright 2016 High Fidelity, Inc.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
@ -34,11 +33,10 @@ StackView {
TabletPreferencesDialog {
id: root
objectName: "GeneralPreferencesDialog"
property string title: "General Preferences"
objectName: "TabletGeneralPreferences"
width: parent.width
height: parent.height
showCategories: ["UI", "Snapshots", "Scripts", "Privacy", "Octree", "HMD", "Sixense Controllers", "Perception Neuron", "Kinect"]
}
}

View file

@ -0,0 +1,42 @@
//
// TabletNetworkingPreferences.qml
//
// Created by Davd Rowe on 7 Mar 2017.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.5
import "tabletWindows"
import "../../dialogs"
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import QtGraphicalEffects 1.0
StackView {
id: profileRoot
initialItem: root
objectName: "stack"
property var eventBridge;
signal sendToScript(var message);
function pushSource(path) {
editRoot.push(Qt.reslovedUrl(path));
}
function popSource() {
}
TabletPreferencesDialog {
id: root
property string title: "Networking Settings"
objectName: "NetworkingPreferences"
width: parent.width
height: parent.height
showCategories: ["Networking"]
}
}

View file

@ -2,6 +2,7 @@ import QtQuick 2.0
import Hifi 1.0
import QtQuick.Controls 1.4
import "../../dialogs"
Item {
id: tabletRoot
objectName: "tabletRoot"
@ -29,7 +30,9 @@ Item {
return openMessage;
}
Component { id: customInputDialogBuilder; TabletCustomQueryDialog { } }
function customInputDialog(properties) {
return customInputDialogBuilder.createObject(tabletRoot, properties);
}
Component { id: fileDialogBuilder; TabletFileDialog { } }

View file

@ -27,12 +27,22 @@ Item {
HifiConstants { id: hifi }
property var sections: []
property var showCategories: []
property bool keyboardEnabled: false
property bool keyboardRaised: false
property bool punctuationMode: false
property var tablet;
function saveAll() {
dialog.forceActiveFocus(); // Accept any text box edits in progress.
for (var i = 0; i < sections.length; ++i) {
var section = sections[i];
section.saveAll();
}
closeDialog();
}
function restoreAll() {
@ -40,22 +50,59 @@ Item {
var section = sections[i];
section.restoreAll();
}
closeDialog();
}
function closeDialog() {
Tablet.getTablet("com.highfidelity.interface.tablet.system").gotoHomeScreen();
}
Rectangle {
id: main
height: parent.height - 40
id: header
height: 90
anchors {
top: parent.top
bottom: footer.top
left: parent.left
right: parent.right
}
z: 100
gradient: Gradient {
GradientStop {
position: 0
color: "#2b2b2b"
}
GradientStop {
position: 1
color: "#1e1e1e"
}
}
RalewayBold {
text: title
size: 26
color: "#34a2c7"
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: hifi.dimensions.contentMargin.x
}
}
Rectangle {
id: main
anchors {
top: header.bottom
bottom: footer.top
left: parent.left
right: parent.right
}
gradient: Gradient {
GradientStop {
position: 0
color: "#2b2b2b"
}
GradientStop {
@ -110,9 +157,7 @@ Item {
}
scrollView.contentHeight = scrollView.getSectionsHeight();
}
Column {
id: prefControls
@ -131,13 +176,30 @@ Item {
}
}
MouseArea {
// Defocuses the current control so that the HMD keyboard gets hidden.
// Created under the footer so that the non-button part of the footer can defocus a control.
id: mouseArea
anchors {
top: parent.top
left: parent.left
right: parent.right
bottom: keyboard.top
}
propagateComposedEvents: true
acceptedButtons: Qt.AllButtons
onPressed: {
parent.forceActiveFocus();
mouse.accepted = false;
}
}
Rectangle {
id: footer
height: 40
anchors {
top: main.bottom
bottom: parent.bottom
bottom: keyboard.top
left: parent.left
right: parent.right
}
@ -145,7 +207,6 @@ Item {
GradientStop {
position: 0
color: "#2b2b2b"
}
GradientStop {
@ -156,7 +217,7 @@ Item {
Row {
anchors {
top: parent,top
verticalCenter: parent.verticalCenter
right: parent.right
rightMargin: hifi.dimensions.contentMargin.x
}
@ -165,15 +226,39 @@ Item {
HifiControls.Button {
text: "Save changes"
color: hifi.buttons.blue
onClicked: root.saveAll()
onClicked: dialog.saveAll()
}
HifiControls.Button {
text: "Cancel"
color: hifi.buttons.white
onClicked: root.restoreAll()
onClicked: dialog.restoreAll()
}
}
}
HifiControls.Keyboard {
id: keyboard
raised: parent.keyboardEnabled && parent.keyboardRaised
numeric: parent.punctuationMode
anchors {
bottom: parent.bottom
left: parent.left
right: parent.right
}
}
Component.onCompleted: {
tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
keyboardEnabled = HMD.active;
}
onKeyboardRaisedChanged: {
if (keyboardEnabled && keyboardRaised) {
var delta = mouseArea.mouseY - (dialog.height - footer.height - keyboard.raisedHeight -hifi.dimensions.controlLineHeight);
if (delta > 0) {
scrollView.contentY += delta;
}
}
}
}

View file

@ -159,6 +159,7 @@ Item {
readonly property vector2d menuPadding: Qt.vector2d(14, 102)
readonly property real scrollbarBackgroundWidth: 18
readonly property real scrollbarHandleWidth: scrollbarBackgroundWidth - 2
readonly property real tabletMenuHeader: 90
}
Item {

View file

@ -499,6 +499,7 @@ bool setupEssentials(int& argc, char** argv) {
DependencyManager::set<TabletScriptingInterface>();
DependencyManager::set<ToolbarScriptingInterface>();
DependencyManager::set<UserActivityLoggerScriptingInterface>();
DependencyManager::set<AssetMappingsScriptingInterface>();
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
DependencyManager::set<SpeechRecognizer>();
@ -1620,14 +1621,7 @@ QString Application::getUserAgent() {
return userAgent;
}
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 tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
TabletProxy* tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
bool messageOpen = tablet->isMessageDialogOpen();
@ -1972,7 +1966,7 @@ void Application::initializeUi() {
rootContext->setContextProperty("Quat", new Quat());
rootContext->setContextProperty("Vec3", new Vec3());
rootContext->setContextProperty("Uuid", new ScriptUUID());
rootContext->setContextProperty("Assets", new AssetMappingsScriptingInterface());
rootContext->setContextProperty("Assets", DependencyManager::get<AssetMappingsScriptingInterface>().data());
rootContext->setContextProperty("AvatarList", DependencyManager::get<AvatarManager>().data());
rootContext->setContextProperty("Users", DependencyManager::get<UsersScriptingInterface>().data());
@ -5839,7 +5833,21 @@ void Application::showAssetServerWidget(QString filePath) {
emit uploadRequest(filePath);
}
};
DependencyManager::get<OffscreenUi>()->show(url, "AssetServer", startUpload);
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
auto tablet = dynamic_cast<TabletProxy*>(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
if (tablet->getToolbarMode()) {
DependencyManager::get<OffscreenUi>()->show(url, "AssetServer", startUpload);
} else {
QQuickItem* tabletRoot = tablet->getTabletRoot();
if (!tabletRoot && !isHMDMode()) {
DependencyManager::get<OffscreenUi>()->show(url, "AssetServer", startUpload);
} else {
static const QUrl url("../../hifi/dialogs/TabletAssetServer.qml");
tablet->pushOntoStack(url);
}
}
startUpload(nullptr, nullptr);
}

View file

@ -294,13 +294,15 @@ Menu::Menu() {
// Settings > General...
action = addActionToQMenuAndActionHash(settingsMenu, MenuOption::Preferences, Qt::CTRL | Qt::Key_Comma, nullptr, nullptr, QAction::PreferencesRole);
connect(action, &QAction::triggered, [] {
DependencyManager::get<OffscreenUi>()->toggle(QString("hifi/dialogs/GeneralPreferencesDialog.qml"), "GeneralPreferencesDialog");
qApp->showDialog(QString("hifi/dialogs/GeneralPreferencesDialog.qml"),
QString("../../hifi/tablet/TabletGeneralPreferences.qml"), "GeneralPreferencesDialog");
});
// Settings > Avatar...
action = addActionToQMenuAndActionHash(settingsMenu, "Avatar...");
connect(action, &QAction::triggered, [] {
DependencyManager::get<OffscreenUi>()->toggle(QString("hifi/dialogs/AvatarPreferencesDialog.qml"), "AvatarPreferencesDialog");
qApp->showDialog(QString("hifi/dialogs/AvatarPreferencesDialog.qml"),
QString("../../hifi/tablet/TabletAvatarPreferences.qml"), "AvatarPreferencesDialog");
});
// Settings > LOD...
@ -550,8 +552,8 @@ Menu::Menu() {
MenuWrapper* networkMenu = developerMenu->addMenu("Network");
action = addActionToQMenuAndActionHash(networkMenu, MenuOption::Networking);
connect(action, &QAction::triggered, [] {
DependencyManager::get<OffscreenUi>()->toggle(QUrl("hifi/dialogs/NetworkingPreferencesDialog.qml"),
"NetworkingPreferencesDialog");
qApp->showDialog(QString("hifi/dialogs/NetworkingPreferencesDialog.qml"),
QString("../../hifi/tablet/TabletNetworkingPreferences.qml"), "NetworkingPreferencesDialog");
});
addActionToQMenuAndActionHash(networkMenu, MenuOption::ReloadContent, 0, qApp, SLOT(reloadResourceCaches()));
addCheckableActionToQMenuAndActionHash(networkMenu,
@ -612,7 +614,8 @@ Menu::Menu() {
action = addActionToQMenuAndActionHash(audioDebugMenu, "Buffers...");
connect(action, &QAction::triggered, [] {
DependencyManager::get<OffscreenUi>()->toggle(QString("hifi/dialogs/AudioPreferencesDialog.qml"), "AudioPreferencesDialog");
qApp->showDialog(QString("hifi/dialogs/AudioPreferencesDialog.qml"),
QString("../../hifi/tablet/TabletAudioPreferences.qml"), "AudioPreferencesDialog");
});
addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::AudioNoiseReduction, 0, true,

View file

@ -20,6 +20,8 @@
#include <AssetClient.h>
#include <QSortFilterProxyModel>
#include "DependencyManager.h"
class AssetMappingModel : public QStandardItemModel {
Q_OBJECT
@ -39,10 +41,12 @@ private:
QHash<QString, QStandardItem*> _pathToItemMap;
};
Q_DECLARE_METATYPE(AssetMappingModel*);
Q_DECLARE_METATYPE(AssetMappingModel*)
class AssetMappingsScriptingInterface : public QObject {
class AssetMappingsScriptingInterface : public QObject, public Dependency {
Q_OBJECT
SINGLETON_DEPENDENCY
Q_PROPERTY(AssetMappingModel* mappingModel READ getAssetMappingModel CONSTANT)
Q_PROPERTY(QAbstractProxyModel* proxyModel READ getProxyModel CONSTANT)
public:

View file

@ -81,6 +81,10 @@ void HMDScriptingInterface::closeTablet() {
_showTablet = false;
}
void HMDScriptingInterface::openTablet() {
_showTablet = true;
}
QScriptValue HMDScriptingInterface::getHUDLookAtPosition2D(QScriptContext* context, QScriptEngine* engine) {
glm::vec3 hudIntersection;
auto instance = DependencyManager::get<HMDScriptingInterface>();

View file

@ -76,6 +76,8 @@ public:
Q_INVOKABLE void closeTablet();
Q_INVOKABLE void openTablet();
signals:
bool shouldShowHandControllersChanged();

View file

@ -258,8 +258,3 @@ QVariant Line3DOverlay::getProperty(const QString& property) {
Line3DOverlay* Line3DOverlay::createClone() const {
return new Line3DOverlay(this);
}
void Line3DOverlay::locationChanged(bool tellPhysics) {
// do nothing
}

View file

@ -48,8 +48,6 @@ public:
virtual Line3DOverlay* createClone() const override;
virtual void locationChanged(bool tellPhysics = true) override;
glm::vec3 getDirection() const { return _direction; }
float getLength() const { return _length; }
glm::vec3 getLocalStart() const { return getLocalPosition(); }

View file

@ -37,13 +37,14 @@
#include <AddressManager.h>
#include "scripting/AccountScriptingInterface.h"
#include "scripting/HMDScriptingInterface.h"
#include "scripting/AssetMappingsScriptingInterface.h"
#include <Preferences.h>
#include <ScriptEngines.h>
#include "FileDialogHelper.h"
#include "avatar/AvatarManager.h"
#include "AudioClient.h"
#include <avatar/AvatarManager.h>
static const float DPI = 30.47f;
static const float INCHES_TO_METERS = 1.0f / 39.3701f;
static const float METERS_TO_INCHES = 39.3701f;
@ -165,6 +166,10 @@ void Web3DOverlay::loadSourceURL() {
_webSurface->getRootContext()->setContextProperty("HMD", DependencyManager::get<HMDScriptingInterface>().data());
_webSurface->getRootContext()->setContextProperty("UserActivityLogger", DependencyManager::get<UserActivityLoggerScriptingInterface>().data());
_webSurface->getRootContext()->setContextProperty("Preferences", DependencyManager::get<Preferences>().data());
_webSurface->getRootContext()->setContextProperty("Vec3", new Vec3());
_webSurface->getRootContext()->setContextProperty("Quat", new Quat());
_webSurface->getRootContext()->setContextProperty("MyAvatar", DependencyManager::get<AvatarManager>()->getMyAvatar().get());
_webSurface->getRootContext()->setContextProperty("Entities", DependencyManager::get<EntityScriptingInterface>().data());
if (_webSurface->getRootItem() && _webSurface->getRootItem()->objectName() == "tabletRoot") {
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
@ -178,16 +183,25 @@ void Web3DOverlay::loadSourceURL() {
_webSurface->getRootContext()->setContextProperty("MyAvatar", DependencyManager::get<AvatarManager>()->getMyAvatar().get());
_webSurface->getRootContext()->setContextProperty("ScriptDiscoveryService", DependencyManager::get<ScriptEngines>().data());
_webSurface->getRootContext()->setContextProperty("Tablet", DependencyManager::get<TabletScriptingInterface>().data());
_webSurface->getRootContext()->setContextProperty("Assets", DependencyManager::get<AssetMappingsScriptingInterface>().data());
_webSurface->getRootContext()->setContextProperty("pathToFonts", "../../");
tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", _webSurface->getRootItem(), _webSurface.data());
// Override min fps for tablet UI, for silky smooth scrolling
_webSurface->setMaxFps(90);
setMaxFPS(90);
}
}
_webSurface->getRootContext()->setContextProperty("globalPosition", vec3toVariant(getPosition()));
}
void Web3DOverlay::setMaxFPS(uint8_t maxFPS) {
_desiredMaxFPS = maxFPS;
if (_webSurface) {
_webSurface->setMaxFps(_desiredMaxFPS);
_currentMaxFPS = _desiredMaxFPS;
}
}
void Web3DOverlay::render(RenderArgs* args) {
if (!_visible || !getParentVisible()) {
return;
@ -197,9 +211,11 @@ void Web3DOverlay::render(RenderArgs* args) {
QSurface * currentSurface = currentContext->surface();
if (!_webSurface) {
_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)
if (_currentMaxFPS != _desiredMaxFPS) {
setMaxFPS(_desiredMaxFPS);
}
loadSourceURL();
_webSurface->resume();
_webSurface->resize(QSize(_resolution.x, _resolution.y));
@ -249,6 +265,10 @@ void Web3DOverlay::render(RenderArgs* args) {
_emitScriptEventConnection = connect(this, &Web3DOverlay::scriptEventReceived, _webSurface.data(), &OffscreenQmlSurface::emitScriptEvent);
_webEventReceivedConnection = connect(_webSurface.data(), &OffscreenQmlSurface::webEventReceived, this, &Web3DOverlay::webEventReceived);
} else {
if (_currentMaxFPS != _desiredMaxFPS) {
setMaxFPS(_desiredMaxFPS);
}
}
vec2 halfSize = getSize() / 2.0f;
@ -404,6 +424,11 @@ void Web3DOverlay::setProperties(const QVariantMap& properties) {
_dpi = dpi.toFloat();
}
auto maxFPS = properties["maxFPS"];
if (maxFPS.isValid()) {
_desiredMaxFPS = maxFPS.toInt();
}
auto showKeyboardFocusHighlight = properties["showKeyboardFocusHighlight"];
if (showKeyboardFocusHighlight.isValid()) {
_showKeyboardFocusHighlight = showKeyboardFocusHighlight.toBool();
@ -423,6 +448,9 @@ QVariant Web3DOverlay::getProperty(const QString& property) {
if (property == "dpi") {
return _dpi;
}
if (property == "maxFPS") {
return _desiredMaxFPS;
}
if (property == "showKeyboardFocusHighlight") {
return _showKeyboardFocusHighlight;
}

View file

@ -31,6 +31,7 @@ public:
QString pickURL();
void loadSourceURL();
void setMaxFPS(uint8_t maxFPS);
virtual void render(RenderArgs* args) override;
virtual const render::ShapeKey getShapeKey() override;
@ -75,6 +76,9 @@ private:
bool _pressed{ false };
QTouchDevice _touchDevice;
uint8_t _desiredMaxFPS { 10 };
uint8_t _currentMaxFPS { 0 };
QMetaObject::Connection _mousePressConnection;
QMetaObject::Connection _mouseReleaseConnection;
QMetaObject::Connection _mouseMoveConnection;

View file

@ -1052,7 +1052,12 @@ void AudioClient::handleAudioInput() {
auto packetType = _shouldEchoToServer ?
PacketType::MicrophoneAudioWithEcho : PacketType::MicrophoneAudioNoEcho;
if (_lastInputLoudness == 0) {
// if the _inputGate closed in this last frame, then we don't actually want
// to send a silent packet, instead, we want to go ahead and encode and send
// the output from the input gate (eventually, this could be crossfaded)
// and allow the codec to properly encode down to silent/zero. If we still
// have _lastInputLoudness of 0 in our NEXT frame, we will send a silent packet
if (_lastInputLoudness == 0 && !_inputGate.closedInLastFrame()) {
packetType = PacketType::SilentAudioFrame;
}
Transform audioTransform;

View file

@ -58,7 +58,6 @@ void AudioNoiseGate::removeDCOffset(int16_t* samples, int numSamples) {
}
}
void AudioNoiseGate::gateSamples(int16_t* samples, int numSamples) {
//
// Impose Noise Gate
@ -77,8 +76,7 @@ void AudioNoiseGate::gateSamples(int16_t* samples, int numSamples) {
// NOISE_GATE_FRAMES_TO_AVERAGE: How many audio frames should we average together to compute noise floor.
// More means better rejection but also can reject continuous things like singing.
// NUMBER_OF_NOISE_SAMPLE_FRAMES: How often should we re-evaluate the noise floor?
float loudness = 0;
int thisSample = 0;
int samplesOverNoiseGate = 0;
@ -142,11 +140,13 @@ void AudioNoiseGate::gateSamples(int16_t* samples, int numSamples) {
_sampleCounter = 0;
}
if (samplesOverNoiseGate > NOISE_GATE_WIDTH) {
_isOpen = true;
_framesToClose = NOISE_GATE_CLOSE_FRAME_DELAY;
} else {
if (--_framesToClose == 0) {
_closedInLastFrame = !_isOpen;
_isOpen = false;
}
}

View file

@ -24,6 +24,7 @@ public:
void removeDCOffset(int16_t* samples, int numSamples);
bool clippedInLastFrame() const { return _didClipInLastFrame; }
bool closedInLastFrame() const { return _closedInLastFrame; }
float getMeasuredFloor() const { return _measuredFloor; }
float getLastLoudness() const { return _lastLoudness; }
@ -40,6 +41,7 @@ private:
float _sampleFrames[NUMBER_OF_NOISE_SAMPLE_FRAMES];
int _sampleCounter;
bool _isOpen;
bool _closedInLastFrame { false };
int _framesToClose;
};

View file

@ -136,9 +136,10 @@ int InboundAudioStream::parseData(ReceivedMessage& message) {
break;
}
case SequenceNumberStats::Early: {
// Packet is early; write droppable silent samples for each of the skipped packets.
// NOTE: we assume that each dropped packet contains the same number of samples
// as the packet we just received.
// Packet is early. Treat the packets as if all the packets between the last
// OnTime packet and this packet were lost. If we're using a codec this will
// also result in allowing the codec to interpolate lost data. Then
// fall through to the "on time" logic to actually handle this packet
int packetsDropped = arrivalInfo._seqDiffFromExpected;
lostAudioData(packetsDropped);
@ -147,7 +148,8 @@ int InboundAudioStream::parseData(ReceivedMessage& message) {
case SequenceNumberStats::OnTime: {
// Packet is on time; parse its data to the ringbuffer
if (message.getType() == PacketType::SilentAudioFrame) {
// FIXME - Some codecs need to know about these silent frames... and can produce better output
// If we recieved a SilentAudioFrame from our sender, we might want to drop
// some of the samples in order to catch up to our desired jitter buffer size.
writeDroppableSilentFrames(networkFrames);
} else {
// note: PCM and no codec are identical
@ -158,7 +160,12 @@ int InboundAudioStream::parseData(ReceivedMessage& message) {
parseAudioData(message.getType(), afterProperties);
} else {
qDebug(audio) << "Codec mismatch: expected" << _selectedCodecName << "got" << codecInPacket << "writing silence";
writeDroppableSilentFrames(networkFrames);
// Since the data in the stream is using a codec that we aren't prepared for,
// we need to let the codec know that we don't have data for it, this will
// allow the codec to interpolate missing data and produce a fade to silence.
lostAudioData(1);
// inform others of the mismatch
auto sendingNode = DependencyManager::get<NodeList>()->nodeWithUUID(message.getSourceID());
emit mismatchedAudioCodec(sendingNode, _selectedCodecName, codecInPacket);
@ -240,6 +247,25 @@ int InboundAudioStream::parseAudioData(PacketType type, const QByteArray& packet
int InboundAudioStream::writeDroppableSilentFrames(int silentFrames) {
// We can't guarentee that all clients have faded the stream down
// to silence and encoded that silence before sending us a
// SilentAudioFrame. If the encoder has truncated the stream it will
// leave the decoder holding some unknown loud state. To handle this
// case we will call the decoder's lostFrame() method, which indicates
// that it should interpolate from its last known state down toward
// silence.
if (_decoder) {
// FIXME - We could potentially use the output from the codec, in which
// case we might get a cleaner fade toward silence. NOTE: The below logic
// attempts to catch up in the event that the jitter buffers have grown.
// The better long term fix is to use the output from the decode, detect
// when it actually reaches silence, and then delete the silent portions
// of the jitter buffers. Or petentially do a cross fade from the decode
// output to silence.
QByteArray decodedBuffer;
_decoder->lostFrame(decodedBuffer);
}
// calculate how many silent frames we should drop.
int silentSamples = silentFrames * _numChannels;
int samplesPerFrame = _ringBuffer.getNumFrameSamples();

View file

@ -434,6 +434,7 @@ void OffscreenQmlSurface::create(QOpenGLContext* shareContext) {
auto rootContext = getRootContext();
rootContext->setContextProperty("urlHandler", new UrlHandler());
rootContext->setContextProperty("resourceDirectoryUrl", QUrl::fromLocalFile(PathUtils::resourcesPath()));
rootContext->setContextProperty("pathToFonts", "../../");
}
static uvec2 clampSize(const uvec2& size, uint32_t maxDimension) {

View file

@ -287,7 +287,8 @@ void TabletProxy::setQmlTabletRoot(QQuickItem* qmlTabletRoot, QObject* qmlOffscr
QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, QVariant(TABLET_SOURCE_URL)));
}
gotoHomeScreen();
// force to the tablet to go to the homescreen
loadHomeScreen(true);
QMetaObject::invokeMethod(_qmlTabletRoot, "setUsername", Q_ARG(const QVariant&, QVariant(getUsername())));
@ -305,6 +306,9 @@ void TabletProxy::setQmlTabletRoot(QQuickItem* qmlTabletRoot, QObject* qmlOffscr
}
}
void TabletProxy::gotoHomeScreen() {
loadHomeScreen(false);
}
void TabletProxy::gotoMenuScreen(const QString& submenu) {
QObject* root = nullptr;
@ -388,8 +392,8 @@ void TabletProxy::popFromStack() {
}
}
void TabletProxy::gotoHomeScreen() {
if (_state != State::Home) {
void TabletProxy::loadHomeScreen(bool forceOntoHomeScreen) {
if (_state != State::Home && ( _state != State::Uninitialized || forceOntoHomeScreen)) {
if (!_toolbarMode && _qmlTabletRoot) {
auto loader = _qmlTabletRoot->findChild<QQuickItem*>("loader");
QObject::connect(loader, SIGNAL(loaded()), this, SLOT(addButtonsToHomeScreen()), Qt::DirectConnection);

View file

@ -202,6 +202,7 @@ protected slots:
void desktopWindowClosed();
protected:
void removeButtonsFromHomeScreen();
void loadHomeScreen(bool forceOntoHomeScreen);
void addButtonsToToolbar();
void removeButtonsFromToolbar();

View file

@ -72,6 +72,9 @@ tablet.screenChanged.connect(onScreenChanged);
AudioDevice.muteToggled.connect(onMuteToggled);
Script.scriptEnding.connect(function () {
if (onAudioScreen) {
tablet.gotoHomeScreen();
}
button.clicked.disconnect(onClicked);
tablet.screenChanged.disconnect(onScreenChanged);
AudioDevice.muteToggled.disconnect(onMuteToggled);

View file

@ -18,7 +18,7 @@
var buttonName = "Settings";
var toolBar = null;
var tablet = null;
var settings = "TabletGeneralSettings.qml"
var settings = "TabletGeneralPreferences.qml"
function onClicked(){
if (tablet) {
tablet.loadQMLSource(settings);

View file

@ -18,13 +18,14 @@ var button;
var buttonName = "GOTO";
var toolBar = null;
var tablet = null;
var onGotoScreen = false;
function onAddressBarShown(visible) {
button.editProperties({isActive: visible});
}
function onClicked(){
DialogsManager.toggleAddressBar();
onGotoScreen = !onGotoScreen;
}
if (Settings.getValue("HUDUIEnabled")) {
@ -49,6 +50,9 @@ button.clicked.connect(onClicked);
DialogsManager.addressBarShown.connect(onAddressBarShown);
Script.scriptEnding.connect(function () {
if (onGotoScreen) {
DialogsManager.toggleAddressBar();
}
button.clicked.disconnect(onClicked);
if (tablet) {
tablet.removeButton(button);

View file

@ -48,6 +48,9 @@
}, POLL_RATE);
Script.scriptEnding.connect(function () {
if (enabled) {
Menu.closeInfoView('InfoView_html/help.html');
}
button.clicked.disconnect(onClicked);
Script.clearInterval(interval);
if (tablet) {

View file

@ -45,6 +45,7 @@ var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
var desktopOnlyViews = ['Mirror', 'Independent Mode', 'Entity Mode'];
function onHmdChanged(isHmd) {
HMD.closeTablet();
if (isHmd) {
button.editProperties({
icon: "icons/tablet-icons/switch-desk-i.svg",

View file

@ -45,6 +45,10 @@ function calcSpawnInfo(hand, height) {
var headPos = (HMD.active && Camera.mode === "first person") ? HMD.position : Camera.position;
var headRot = (HMD.active && Camera.mode === "first person") ? HMD.orientation : Camera.orientation;
if (!hand) {
hand = NO_HANDS;
}
if (HMD.active && hand !== NO_HANDS) {
var handController = getControllerWorldLocation(hand, true);
var controllerPosition = handController.position;
@ -96,7 +100,7 @@ function calcSpawnInfo(hand, height) {
* @param hand [number] -1 indicates no hand, Controller.Standard.RightHand or Controller.Standard.LeftHand
* @param clientOnly [bool] true indicates tablet model is only visible to client.
*/
WebTablet = function (url, width, dpi, hand, clientOnly) {
WebTablet = function (url, width, dpi, hand, clientOnly, location) {
var _this = this;
@ -134,6 +138,10 @@ WebTablet = function (url, width, dpi, hand, clientOnly) {
// compute position, rotation & parentJointIndex of the tablet
this.calculateTabletAttachmentProperties(hand, true, tabletProperties);
if (location) {
tabletProperties.localPosition = location.localPosition;
tabletProperties.localRotation = location.localRotation;
}
this.cleanUpOldTablets();

View file

@ -121,6 +121,7 @@ function onClick() {
if (onMarketplaceScreen) {
// for toolbar-mode: go back to home screen, this will close the window.
tablet.gotoHomeScreen();
onMarketplaceScreen = false;
} else {
var entity = HMD.tabletID;
Entities.editEntity(entity, {textures: JSON.stringify({"tex.close": HOME_BUTTON_TEXTURE})});
@ -140,6 +141,9 @@ tablet.screenChanged.connect(onScreenChanged);
Entities.canWriteAssetsChanged.connect(onCanWriteAssetsChanged);
Script.scriptEnding.connect(function () {
if (onMarketplaceScreen) {
tablet.gotoHomeScreen();
}
tablet.removeButton(marketplaceButton);
tablet.screenChanged.disconnect(onScreenChanged);
Entities.canWriteAssetsChanged.disconnect(onCanWriteAssetsChanged);

View file

@ -48,6 +48,9 @@ var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-
tablet.screenChanged.connect(onScreenChanged);
Script.scriptEnding.connect(function () {
if (onMenuScreen) {
tablet.gotoHomeScreen();
}
button.clicked.disconnect(onClicked);
tablet.removeButton(button);
tablet.screenChanged.disconnect(onScreenChanged);

View file

@ -696,6 +696,9 @@ function clearLocalQMLDataAndClosePAL() {
}
function shutdown() {
if (onPalScreen) {
tablet.gotoHomeScreen();
}
button.clicked.disconnect(onTabletButtonClicked);
tablet.removeButton(button);
tablet.screenChanged.disconnect(onTabletScreenChanged);

View file

@ -191,12 +191,12 @@ function resetButtons(pathStillSnapshot, pathAnimatedSnapshot, notify) {
if (clearOverlayWhenMoving) {
MyAvatar.setClearOverlayWhenMoving(true); // not until after the share dialog
}
HMD.openTablet();
}
function processingGif() {
// show hud
Reticle.visible = reticleVisible;
button.clicked.disconnect(onClicked);
buttonConnected = false;
// show overlays if they were on
@ -211,8 +211,10 @@ Window.snapshotShared.connect(snapshotShared);
Window.processingGif.connect(processingGif);
Script.scriptEnding.connect(function () {
button.clicked.disconnect(onClicked);
buttonConnected = false;
if (buttonConnected) {
button.clicked.disconnect(onClicked);
buttonConnected = false;
}
if (tablet) {
tablet.removeButton(button);
}

View file

@ -12,21 +12,47 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
/* global Script, HMD, WebTablet, UIWebTablet, UserActivityLogger, Settings, Entities, Messages, Tablet, Overlays, MyAvatar */
/* global Script, HMD, WebTablet, UIWebTablet, UserActivityLogger, Settings, Entities, Messages, Tablet, Overlays,
MyAvatar, Menu */
(function() { // BEGIN LOCAL_SCOPE
var tabletShown = false;
var tabletLocation = null;
var tabletRezzed = false;
var activeHand = null;
var DEFAULT_WIDTH = 0.4375;
var DEFAULT_TABLET_SCALE = 100;
var preMakeTime = Date.now();
var validCheckTime = Date.now();
var debugTablet = false;
UIWebTablet = null;
Script.include("../libraries/WebTablet.js");
function showTabletUI() {
tabletShown = true;
print("show tablet-ui");
function tabletIsValid() {
if (!UIWebTablet) {
return false;
}
if (UIWebTablet.tabletIsOverlay && Overlays.getProperty(HMD.tabletID, "type") != "model") {
if (debugTablet) {
print("TABLET is invalid due to frame: " + JSON.stringify(Overlays.getProperty(HMD.tabletID, "type")));
}
return false;
}
if (Overlays.getProperty(HMD.homeButtonID, "type") != "sphere" ||
Overlays.getProperty(HMD.tabletScreenID, "type") != "web3d") {
if (debugTablet) {
print("TABLET is invalid due to other");
}
return false;
}
return true;
}
var DEFAULT_WIDTH = 0.4375;
var DEFAULT_TABLET_SCALE = 100;
function rezTablet() {
if (debugTablet) {
print("TABLET rezzing");
}
var toolbarMode = Tablet.getTablet("com.highfidelity.interface.tablet.system").toolbarMode;
var TABLET_SCALE = DEFAULT_TABLET_SCALE;
if (toolbarMode) {
@ -34,37 +60,98 @@
} else {
TABLET_SCALE = Settings.getValue("hmdTabletScale") || DEFAULT_TABLET_SCALE;
}
UIWebTablet = new WebTablet("qml/hifi/tablet/TabletRoot.qml", DEFAULT_WIDTH * (TABLET_SCALE / 100), null, activeHand, true);
UIWebTablet = new WebTablet("qml/hifi/tablet/TabletRoot.qml",
DEFAULT_WIDTH * (TABLET_SCALE / 100),
null, activeHand, true);
UIWebTablet.register();
HMD.tabletID = UIWebTablet.tabletEntityID;
HMD.homeButtonID = UIWebTablet.homeButtonID;
HMD.tabletScreenID = UIWebTablet.webOverlayID;
tabletRezzed = true;
}
function showTabletUI() {
tabletShown = true;
if (!tabletRezzed) {
rezTablet(false);
}
if (UIWebTablet && tabletRezzed) {
if (debugTablet) {
print("TABLET in showTabletUI, already rezzed");
}
var tabletProperties = {};
UIWebTablet.calculateTabletAttachmentProperties(activeHand, true, tabletProperties);
tabletProperties.visible = true;
if (UIWebTablet.tabletIsOverlay) {
Overlays.editOverlay(HMD.tabletID, tabletProperties);
}
Overlays.editOverlay(HMD.homeButtonID, { visible: true });
Overlays.editOverlay(HMD.tabletScreenID, { visible: true });
Overlays.editOverlay(HMD.tabletScreenID, { maxFPS: 90 });
}
}
function hideTabletUI() {
tabletShown = false;
print("hide tablet-ui");
if (!UIWebTablet) {
return;
}
if (UIWebTablet.tabletIsOverlay) {
if (debugTablet) {
print("TABLET hide");
}
if (Settings.getValue("tabletVisibleToOthers")) {
closeTabletUI();
} else {
// Overlays.editOverlay(HMD.tabletID, { localPosition: { x: -1000, y: 0, z:0 } });
Overlays.editOverlay(HMD.tabletID, { visible: false });
Overlays.editOverlay(HMD.homeButtonID, { visible: false });
Overlays.editOverlay(HMD.tabletScreenID, { visible: false });
Overlays.editOverlay(HMD.tabletScreenID, { maxFPS: 1 });
}
} else {
closeTabletUI();
}
}
function closeTabletUI() {
tabletShown = false;
if (UIWebTablet) {
if (UIWebTablet.onClose) {
UIWebTablet.onClose();
}
tabletLocation = UIWebTablet.getLocation();
if (debugTablet) {
print("TABLET close");
}
UIWebTablet.unregister();
UIWebTablet.destroy();
UIWebTablet = null;
HMD.tabletID = null;
HMD.homeButtonID = null;
HMD.tabletScreenID = null;
} else if (debugTablet) {
print("TABLET closeTabletUI, UIWebTablet is null");
}
tabletRezzed = false;
}
function updateShowTablet() {
var MSECS_PER_SEC = 1000.0;
var now = Date.now();
// close the WebTablet if it we go into toolbar mode.
var toolbarMode = Tablet.getTablet("com.highfidelity.interface.tablet.system").toolbarMode;
var visibleToOthers = Settings.getValue("tabletVisibleToOthers");
if (tabletShown && toolbarMode) {
hideTabletUI();
closeTabletUI();
HMD.closeTablet();
return;
}
@ -78,19 +165,48 @@
tablet.updateAudioBar(currentMicLevel);
}
if (tabletShown && UIWebTablet && Overlays.getOverlayType(UIWebTablet.webOverlayID) != "web3d") {
// when we switch domains, the tablet entity gets destroyed and recreated. this causes
// the overlay to be deleted, but not recreated. If the overlay is deleted for this or any
// other reason, close the tablet.
hideTabletUI();
HMD.closeTablet();
} else if (HMD.showTablet && !tabletShown && !toolbarMode) {
UserActivityLogger.openedTablet(Settings.getValue("tabletVisibleToOthers"));
if (validCheckTime - now > MSECS_PER_SEC) {
validCheckTime = now;
if (tabletRezzed && UIWebTablet && !tabletIsValid()) {
// when we switch domains, the tablet entity gets destroyed and recreated. this causes
// the overlay to be deleted, but not recreated. If the overlay is deleted for this or any
// other reason, close the tablet.
closeTabletUI();
HMD.closeTablet();
if (debugTablet) {
print("TABLET autodestroying");
}
}
}
if (HMD.showTablet && !tabletShown && !toolbarMode) {
UserActivityLogger.openedTablet(visibleToOthers);
showTabletUI();
} else if (!HMD.showTablet && tabletShown) {
UserActivityLogger.closedTablet();
hideTabletUI();
if (visibleToOthers) {
closeTabletUI();
} else {
hideTabletUI();
}
}
// if the tablet is an overlay, attempt to pre-create it and then hide it so that when it's
// summoned, it will appear quickly.
if (!toolbarMode && !visibleToOthers) {
if (now - preMakeTime > MSECS_PER_SEC) {
preMakeTime = now;
if (!tabletIsValid()) {
closeTabletUI();
rezTablet(false);
tabletShown = false;
} else if (!tabletShown) {
hideTabletUI();
}
}
}
}
function toggleHand(channel, hand, senderUUID, localOnly) {
@ -131,7 +247,9 @@
}
Script.scriptEnding.connect(function () {
Entities.deleteEntity(HMD.tabletID);
var tabletID = HMD.tabletID;
Entities.deleteEntity(tabletID);
Overlays.deleteOverlay(tabletID)
HMD.tabletID = null;
HMD.homeButtonID = null;
HMD.tabletScreenID = null;

View file

@ -114,6 +114,9 @@
tablet.screenChanged.connect(onScreenChanged);
function cleanup() {
if (onUsersScreen) {
tablet.gotoHomeScreen();
}
button.clicked.disconnect(onClicked);
tablet.removeButton(button);
}

File diff suppressed because it is too large Load diff