diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp
index 2583e15760..b95c429b2d 100644
--- a/assignment-client/src/audio/AudioMixer.cpp
+++ b/assignment-client/src/audio/AudioMixer.cpp
@@ -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;
diff --git a/assignment-client/src/audio/AudioMixerSlave.cpp b/assignment-client/src/audio/AudioMixerSlave.cpp
index 6b53de89c2..d01d961e33 100644
--- a/assignment-client/src/audio/AudioMixerSlave.cpp
+++ b/assignment-client/src/audio/AudioMixerSlave.cpp
@@ -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;
}
diff --git a/assignment-client/src/audio/AudioMixerStats.cpp b/assignment-client/src/audio/AudioMixerStats.cpp
index a831210871..4cfdd55167 100644
--- a/assignment-client/src/audio/AudioMixerStats.cpp
+++ b/assignment-client/src/audio/AudioMixerStats.cpp
@@ -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;
diff --git a/assignment-client/src/audio/AudioMixerStats.h b/assignment-client/src/audio/AudioMixerStats.h
index 77ac8b985d..f4ba9db769 100644
--- a/assignment-client/src/audio/AudioMixerStats.h
+++ b/assignment-client/src/audio/AudioMixerStats.h
@@ -19,6 +19,7 @@
struct AudioMixerStats {
int sumStreams { 0 };
int sumListeners { 0 };
+ int sumListenersSilent { 0 };
int totalMixes { 0 };
diff --git a/cmake/macros/LinkHifiLibraries.cmake b/cmake/macros/LinkHifiLibraries.cmake
index 3767dc7131..de4ff23863 100644
--- a/cmake/macros/LinkHifiLibraries.cmake
+++ b/cmake/macros/LinkHifiLibraries.cmake
@@ -21,7 +21,7 @@ macro(LINK_HIFI_LIBRARIES)
include_directories("${HIFI_LIBRARY_DIR}/${HIFI_LIBRARY}/src")
include_directories("${CMAKE_BINARY_DIR}/libraries/${HIFI_LIBRARY}/shaders")
- add_dependencies(${TARGET_NAME} ${HIFI_LIBRARY})
+ #add_dependencies(${TARGET_NAME} ${HIFI_LIBRARY})
# link the actual library - it is static so don't bubble it up
target_link_libraries(${TARGET_NAME} ${HIFI_LIBRARY})
diff --git a/interface/resources/icons/create-icons/20-text-01.svg b/interface/resources/icons/create-icons/20-text-01.svg
new file mode 100644
index 0000000000..337f3b70e3
--- /dev/null
+++ b/interface/resources/icons/create-icons/20-text-01.svg
@@ -0,0 +1,26 @@
+
+
+
diff --git a/interface/resources/icons/create-icons/21-cube-01.svg b/interface/resources/icons/create-icons/21-cube-01.svg
new file mode 100644
index 0000000000..21a980ca35
--- /dev/null
+++ b/interface/resources/icons/create-icons/21-cube-01.svg
@@ -0,0 +1,17 @@
+
+
+
diff --git a/interface/resources/icons/create-icons/22-sphere-01.svg b/interface/resources/icons/create-icons/22-sphere-01.svg
new file mode 100644
index 0000000000..5080a16e78
--- /dev/null
+++ b/interface/resources/icons/create-icons/22-sphere-01.svg
@@ -0,0 +1,21 @@
+
+
+
diff --git a/interface/resources/icons/create-icons/23-zone-01.svg b/interface/resources/icons/create-icons/23-zone-01.svg
new file mode 100644
index 0000000000..5428257893
--- /dev/null
+++ b/interface/resources/icons/create-icons/23-zone-01.svg
@@ -0,0 +1,38 @@
+
+
+
diff --git a/interface/resources/icons/create-icons/24-light-01.svg b/interface/resources/icons/create-icons/24-light-01.svg
new file mode 100644
index 0000000000..028ea22793
--- /dev/null
+++ b/interface/resources/icons/create-icons/24-light-01.svg
@@ -0,0 +1,34 @@
+
+
+
diff --git a/interface/resources/icons/create-icons/25-web-1-01.svg b/interface/resources/icons/create-icons/25-web-1-01.svg
new file mode 100644
index 0000000000..4f0eccc11e
--- /dev/null
+++ b/interface/resources/icons/create-icons/25-web-1-01.svg
@@ -0,0 +1,30 @@
+
+
+
diff --git a/interface/resources/icons/create-icons/90-particles-01.svg b/interface/resources/icons/create-icons/90-particles-01.svg
new file mode 100644
index 0000000000..5e0105d7cd
--- /dev/null
+++ b/interface/resources/icons/create-icons/90-particles-01.svg
@@ -0,0 +1,29 @@
+
+
+
diff --git a/interface/resources/icons/create-icons/94-model-01.svg b/interface/resources/icons/create-icons/94-model-01.svg
new file mode 100644
index 0000000000..5d8c4c5eca
--- /dev/null
+++ b/interface/resources/icons/create-icons/94-model-01.svg
@@ -0,0 +1,20 @@
+
+
+
diff --git a/interface/resources/images/sphere-01.svg b/interface/resources/images/sphere-01.svg
new file mode 100644
index 0000000000..975199c8da
--- /dev/null
+++ b/interface/resources/images/sphere-01.svg
@@ -0,0 +1,108 @@
+
+
+
diff --git a/interface/resources/qml/controls-uit/BaseWebView.qml b/interface/resources/qml/controls-uit/BaseWebView.qml
index 763e6530fb..9c22a8ff8c 100644
--- a/interface/resources/qml/controls-uit/BaseWebView.qml
+++ b/interface/resources/qml/controls-uit/BaseWebView.qml
@@ -15,7 +15,7 @@ import HFWebEngineProfile 1.0
WebEngineView {
id: root
- profile: desktop.browserProfile
+ // profile: desktop.browserProfile
Component.onCompleted: {
console.log("Connecting JS messaging to Hifi Logging")
diff --git a/interface/resources/qml/controls-uit/TabletComboBox.qml b/interface/resources/qml/controls-uit/TabletComboBox.qml
index e5dec315e5..7361833a45 100644
--- a/interface/resources/qml/controls-uit/TabletComboBox.qml
+++ b/interface/resources/qml/controls-uit/TabletComboBox.qml
@@ -14,7 +14,6 @@ import QtQuick.Controls.Styles 1.4
import "../styles-uit"
import "../controls-uit" as HifiControls
-import "." as VrControls
FocusScope {
id: root
@@ -119,14 +118,14 @@ FocusScope {
}
function showList() {
- var r = 20//desktop.mapFromItem(root, 0, 0, root.width, root.height);
- var y = 200;
- var bottom = 0 + scrollView.height;
+ var r = mapFromItem(root, 0, 0, root.width, root.height);
+ var y = r.y + r.height;
+ var bottom = y + scrollView.height;
if (bottom > 720) {
- y -= bottom - 720 + 8;
+ y -= bottom - tabletRoot.height + 8;
}
- scrollView.x = 0;
- scrollView.y = 0;
+ scrollView.x = r.x;
+ scrollView.y = y;
popup.visible = true;
popup.forceActiveFocus();
listView.currentIndex = root.currentIndex;
@@ -141,6 +140,7 @@ FocusScope {
FocusScope {
id: popup
+ z: 12
parent: parent
anchors.fill: parent
visible: false
diff --git a/interface/resources/qml/dialogs/TabletCustomQueryDialog.qml b/interface/resources/qml/dialogs/TabletCustomQueryDialog.qml
new file mode 100644
index 0000000000..6e5bc50d32
--- /dev/null
+++ b/interface/resources/qml/dialogs/TabletCustomQueryDialog.qml
@@ -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();
+ }
+}
diff --git a/interface/resources/qml/dialogs/TabletFileDialog.qml b/interface/resources/qml/dialogs/TabletFileDialog.qml
new file mode 100644
index 0000000000..4fecd4b523
--- /dev/null
+++ b/interface/resources/qml/dialogs/TabletFileDialog.qml
@@ -0,0 +1,782 @@
+//
+// FileDialog.qml
+//
+// Created by Dante Ruiz on 23 Feb 2017
+// Copyright 2015 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+import QtQuick 2.5
+import QtQuick.Controls 1.4
+import Qt.labs.folderlistmodel 2.1
+import Qt.labs.settings 1.0
+import QtQuick.Controls.Styles 1.4
+import QtQuick.Dialogs 1.2 as OriginalDialogs
+
+import ".."
+import "../controls-uit"
+import "../styles-uit"
+import "../windows"
+
+import "fileDialog"
+
+//FIXME implement shortcuts for favorite location
+TabletModalWindow {
+ id: root
+ anchors.fill: parent
+ width: parent.width
+ height: parent.height
+ HifiConstants { id: hifi }
+
+ Settings {
+ category: "FileDialog"
+ property alias width: root.width
+ property alias height: root.height
+ property alias x: root.x
+ property alias y: root.y
+ }
+
+
+ // Set from OffscreenUi::getOpenFile()
+ property alias caption: root.title;
+ // Set from OffscreenUi::getOpenFile()
+ property alias dir: fileTableModel.folder;
+ // Set from OffscreenUi::getOpenFile()
+ property alias filter: selectionType.filtersString;
+ // Set from OffscreenUi::getOpenFile()
+ property int options; // <-- FIXME unused
+
+ property string iconText: root.title !== "" ? hifi.glyphs.scriptUpload : ""
+ property int iconSize: 40
+
+ property bool selectDirectory: false;
+ property bool showHidden: false;
+ // FIXME implement
+ property bool multiSelect: false;
+ property bool saveDialog: false;
+ property var helper: fileDialogHelper
+ property alias model: fileTableView.model
+ property var drives: helper.drives()
+
+ property int titleWidth: 0
+
+ signal selectedFile(var file);
+ signal canceled();
+
+ Component.onCompleted: {
+ fileDialogItem.keyboardEnabled = HMD.active;
+
+ // HACK: The following lines force the model to initialize properly such that the go-up button
+ // works properly from the initial screen.
+ var initialFolder = folderListModel.folder;
+ fileTableModel.folder = helper.pathToUrl(drives[0]);
+ fileTableModel.folder = initialFolder;
+
+ iconText = root.title !== "" ? hifi.glyphs.scriptUpload : "";
+
+ // Clear selection when click on external frame.
+ //frameClicked.connect(function() { d.clearSelection(); });
+
+ if (selectDirectory) {
+ currentSelection.text = d.capitalizeDrive(helper.urlToPath(initialFolder));
+ d.currentSelectionIsFolder = true;
+ d.currentSelectionUrl = initialFolder;
+ }
+
+ helper.contentsChanged.connect(function() {
+ if (folderListModel) {
+ // Make folderListModel refresh.
+ var save = folderListModel.folder;
+ folderListModel.folder = "";
+ folderListModel.folder = save;
+ }
+ });
+
+ fileTableView.forceActiveFocus();
+ }
+
+ TabletModalFrame {
+ id: fileDialogItem
+ width: parent.width - 6
+ height: parent.height - 6
+
+ anchors {
+ horizontalCenter: root.horizontalCenter
+ verticalCenter: root.verticalCenter
+ }
+
+ property bool keyboardEnabled: false
+ property bool keyboardRaised: false
+ property bool punctuationMode: false
+
+ MouseArea {
+ // Clear selection when click on internal unused area.
+ anchors.fill: parent
+ onClicked: {
+ d.clearSelection();
+ }
+ }
+
+ Row {
+ id: navControls
+ anchors {
+ top: parent.top
+ topMargin: (fileDialogItem.hasTitle ? (fileDialogItem.frameMarginTop + hifi.dimensions.modalDialogMargin.y) : hifi.dimension.modalDialogMargin.y)
+ left: parent.left
+ leftMargin: hifi.dimensions.contentSpacing.x
+ }
+ spacing: hifi.dimensions.contentSpacing.x
+
+ GlyphButton {
+ id: upButton
+ glyph: hifi.glyphs.levelUp
+ width: height
+ size: 30
+ enabled: fileTableModel.parentFolder && fileTableModel.parentFolder !== ""
+ onClicked: d.navigateUp();
+ }
+
+ GlyphButton {
+ id: homeButton
+ property var destination: helper.home();
+ glyph: hifi.glyphs.home
+ size: 28
+ width: height
+ enabled: d.homeDestination ? true : false
+ onClicked: d.navigateHome();
+ }
+ }
+
+ TabletComboBox {
+ id: pathSelector
+ z: 10
+ anchors {
+ top: parent.top
+ topMargin: (fileDialogItem.hasTitle ? (fileDialogItem.frameMarginTop + hifi.dimensions.modalDialogMargin.y) : hifi.dimension.modalDialogMargin.y)
+ left: navControls.right
+ leftMargin: hifi.dimensions.contentSpacing.x
+ right: parent.right
+ }
+
+ property var lastValidFolder: helper.urlToPath(fileTableModel.folder)
+
+ function calculatePathChoices(folder) {
+ var folders = folder.split("/"),
+ choices = [],
+ i, length;
+
+ if (folders[folders.length - 1] === "") {
+ folders.pop();
+ }
+
+ choices.push(folders[0]);
+
+ for (i = 1, length = folders.length; i < length; i++) {
+ choices.push(choices[i - 1] + "/" + folders[i]);
+ }
+
+ if (folders[0] === "") {
+ // Special handling for OSX root dir.
+ choices[0] = "/";
+ }
+
+ choices.reverse();
+
+ if (drives && drives.length > 1) {
+ choices.push("This PC");
+ }
+
+ if (choices.length > 0) {
+ pathSelector.model = choices;
+ }
+ }
+
+ onLastValidFolderChanged: {
+ var folder = d.capitalizeDrive(lastValidFolder);
+ calculatePathChoices(folder);
+ }
+
+ onCurrentTextChanged: {
+ var folder = currentText;
+
+ if (/^[a-zA-z]:$/.test(folder)) {
+ folder = "file:///" + folder + "/";
+ } else if (folder === "This PC") {
+ folder = "file:///";
+ } else {
+ folder = helper.pathToUrl(folder);
+ }
+
+ if (helper.urlToPath(folder).toLowerCase() !== helper.urlToPath(fileTableModel.folder).toLowerCase()) {
+ if (root.selectDirectory) {
+ currentSelection.text = currentText !== "This PC" ? currentText : "";
+ d.currentSelectionUrl = helper.pathToUrl(currentText);
+ }
+ fileTableModel.folder = folder;
+ fileTableView.forceActiveFocus();
+ }
+ }
+ }
+
+ QtObject {
+ id: d
+ property var currentSelectionUrl;
+ readonly property string currentSelectionPath: helper.urlToPath(currentSelectionUrl);
+ property bool currentSelectionIsFolder;
+ property var backStack: []
+ property var tableViewConnection: Connections { target: fileTableView; onCurrentRowChanged: d.update(); }
+ property var modelConnection: Connections { target: fileTableModel; onFolderChanged: d.update(); }
+ property var homeDestination: helper.home();
+
+ function capitalizeDrive(path) {
+ // Consistently capitalize drive letter for Windows.
+ if (/[a-zA-Z]:/.test(path)) {
+ return path.charAt(0).toUpperCase() + path.slice(1);
+ }
+ return path;
+ }
+
+ function update() {
+ var row = fileTableView.currentRow;
+
+ if (row === -1) {
+ if (!root.selectDirectory) {
+ currentSelection.text = "";
+ currentSelectionIsFolder = false;
+ }
+ return;
+ }
+
+ currentSelectionUrl = helper.pathToUrl(fileTableView.model.get(row).filePath);
+ currentSelectionIsFolder = fileTableView.model.isFolder(row);
+ if (root.selectDirectory || !currentSelectionIsFolder) {
+ currentSelection.text = capitalizeDrive(helper.urlToPath(currentSelectionUrl));
+ } else {
+ currentSelection.text = "";
+ }
+ }
+
+ function navigateUp() {
+ if (fileTableModel.parentFolder && fileTableModel.parentFolder !== "") {
+ fileTableModel.folder = fileTableModel.parentFolder;
+ return true;
+ }
+ }
+
+ function navigateHome() {
+ fileTableModel.folder = homeDestination;
+ return true;
+ }
+
+ function clearSelection() {
+ fileTableView.selection.clear();
+ fileTableView.currentRow = -1;
+ update();
+ }
+ }
+
+ FolderListModel {
+ id: folderListModel
+ nameFilters: selectionType.currentFilter
+ showDirsFirst: true
+ showDotAndDotDot: false
+ showFiles: !root.selectDirectory
+ Component.onCompleted: {
+ showFiles = !root.selectDirectory
+ }
+
+ onFolderChanged: {
+ fileTableModel.update(); // Update once the data from the folder change is available.
+ }
+
+ function getItem(index, field) {
+ return get(index, field);
+ }
+ }
+
+ ListModel {
+ // Emulates FolderListModel but contains drive data.
+ id: driveListModel
+
+ property int count: 1
+
+ Component.onCompleted: initialize();
+
+ function initialize() {
+ var drive,
+ i;
+
+ count = drives.length;
+
+ for (i = 0; i < count; i++) {
+ drive = drives[i].slice(0, -1); // Remove trailing "/".
+ append({
+ fileName: drive,
+ fileModified: new Date(0),
+ fileSize: 0,
+ filePath: drive + "/",
+ fileIsDir: true,
+ fileNameSort: drive.toLowerCase()
+ });
+ }
+ }
+
+ function getItem(index, field) {
+ return get(index)[field];
+ }
+ }
+
+ ListModel {
+ id: fileTableModel
+
+ // FolderListModel has a couple of problems:
+ // 1) Files and directories sort case-sensitively: https://bugreports.qt.io/browse/QTBUG-48757
+ // 2) Cannot browse up to the "computer" level to view Windows drives: https://bugreports.qt.io/browse/QTBUG-42901
+ //
+ // To solve these problems an intermediary ListModel is used that implements proper sorting and can be populated with
+ // drive information when viewing at the computer level.
+
+ property var folder
+ property int sortOrder: Qt.AscendingOrder
+ property int sortColumn: 0
+ property var model: folderListModel
+ property string parentFolder: calculateParentFolder();
+
+ readonly property string rootFolder: "file:///"
+
+ function calculateParentFolder() {
+ if (model === folderListModel) {
+ if (folderListModel.parentFolder.toString() === "" && driveListModel.count > 1) {
+ return rootFolder;
+ } else {
+ return folderListModel.parentFolder;
+ }
+ } else {
+ return "";
+ }
+ }
+
+ onFolderChanged: {
+ if (folder === rootFolder) {
+ model = driveListModel;
+ helper.monitorDirectory("");
+ update();
+ } else {
+ var needsUpdate = model === driveListModel && folder === folderListModel.folder;
+
+ model = folderListModel;
+ folderListModel.folder = folder;
+ helper.monitorDirectory(helper.urlToPath(folder));
+
+ if (needsUpdate) {
+ update();
+ }
+ }
+ }
+
+ function isFolder(row) {
+ if (row === -1) {
+ return false;
+ }
+ return get(row).fileIsDir;
+ }
+
+ function update() {
+ var dataFields = ["fileName", "fileModified", "fileSize"],
+ sortFields = ["fileNameSort", "fileModified", "fileSize"],
+ dataField = dataFields[sortColumn],
+ sortField = sortFields[sortColumn],
+ sortValue,
+ fileName,
+ fileIsDir,
+ comparisonFunction,
+ lower,
+ middle,
+ upper,
+ rows = 0,
+ i;
+
+ clear();
+
+ comparisonFunction = sortOrder === Qt.AscendingOrder
+ ? function(a, b) { return a < b; }
+ : function(a, b) { return a > b; }
+
+ for (i = 0; i < model.count; i++) {
+ fileName = model.getItem(i, "fileName");
+ fileIsDir = model.getItem(i, "fileIsDir");
+
+ sortValue = model.getItem(i, dataField);
+ if (dataField === "fileName") {
+ // Directories first by prefixing a "*".
+ // Case-insensitive.
+ sortValue = (fileIsDir ? "*" : "") + sortValue.toLowerCase();
+ }
+
+ lower = 0;
+ upper = rows;
+ while (lower < upper) {
+ middle = Math.floor((lower + upper) / 2);
+ var lessThan;
+ if (comparisonFunction(sortValue, get(middle)[sortField])) {
+ lessThan = true;
+ upper = middle;
+ } else {
+ lessThan = false;
+ lower = middle + 1;
+ }
+ }
+
+ insert(lower, {
+ fileName: fileName,
+ fileModified: (fileIsDir ? new Date(0) : model.getItem(i, "fileModified")),
+ fileSize: model.getItem(i, "fileSize"),
+ filePath: model.getItem(i, "filePath"),
+ fileIsDir: fileIsDir,
+ fileNameSort: (fileIsDir ? "*" : "") + fileName.toLowerCase()
+ });
+
+ rows++;
+ }
+
+ d.clearSelection();
+ }
+ }
+
+ Table {
+ id: fileTableView
+ colorScheme: hifi.colorSchemes.light
+ anchors {
+ top: navControls.bottom
+ topMargin: hifi.dimensions.contentSpacing.y
+ left: parent.left
+ right: parent.right
+ bottom: currentSelection.top
+ bottomMargin: hifi.dimensions.contentSpacing.y + currentSelection.controlHeight - currentSelection.height
+ }
+ headerVisible: !selectDirectory
+ onClicked: navigateToRow(row);
+ onDoubleClicked: navigateToRow(row);
+ focus: true
+ Keys.onReturnPressed: navigateToCurrentRow();
+ Keys.onEnterPressed: navigateToCurrentRow();
+
+ sortIndicatorColumn: 0
+ sortIndicatorOrder: Qt.AscendingOrder
+ sortIndicatorVisible: true
+
+ model: fileTableModel
+
+ function updateSort() {
+ model.sortOrder = sortIndicatorOrder;
+ model.sortColumn = sortIndicatorColumn;
+ model.update();
+ }
+
+ onSortIndicatorColumnChanged: { updateSort(); }
+
+ onSortIndicatorOrderChanged: { updateSort(); }
+
+ itemDelegate: Item {
+ clip: true
+
+ //FontLoader { id: firaSansSemiBold; source: "../../fonts/FiraSans-SemiBold.ttf"; }
+ //FontLoader { id: firaSansRegular; source: "../../fonts/FiraSans-Regular.ttf"; }
+
+ FiraSansSemiBold {
+ text: getText();
+ elide: styleData.elideMode
+ anchors {
+ left: parent.left
+ leftMargin: hifi.dimensions.tablePadding
+ right: parent.right
+ rightMargin: hifi.dimensions.tablePadding
+ verticalCenter: parent.verticalCenter
+ }
+ size: hifi.fontSizes.tableText
+ color: hifi.colors.baseGrayHighlight
+ //font.family: (styleData.row !== -1 && fileTableView.model.get(styleData.row).fileIsDir)
+ //? firaSansSemiBold.name : firaSansRegular.name
+
+ function getText() {
+ if (styleData.row === -1) {
+ return styleData.value;
+ }
+
+ switch (styleData.column) {
+ case 1: return fileTableView.model.get(styleData.row).fileIsDir ? "" : styleData.value;
+ case 2: return fileTableView.model.get(styleData.row).fileIsDir ? "" : formatSize(styleData.value);
+ default: return styleData.value;
+ }
+ }
+ function formatSize(size) {
+ var suffixes = [ "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" ];
+ var suffixIndex = 0
+ while ((size / 1024.0) > 1.1) {
+ size /= 1024.0;
+ ++suffixIndex;
+ }
+
+ size = Math.round(size*1000)/1000;
+ size = size.toLocaleString()
+
+ return size + " " + suffixes[suffixIndex];
+ }
+ }
+ }
+
+ TableViewColumn {
+ id: fileNameColumn
+ role: "fileName"
+ title: "Name"
+ width: (selectDirectory ? 1.0 : 0.5) * fileTableView.width
+ movable: false
+ resizable: true
+ }
+ TableViewColumn {
+ id: fileMofifiedColumn
+ role: "fileModified"
+ title: "Date"
+ width: 0.3 * fileTableView.width
+ movable: false
+ resizable: true
+ visible: !selectDirectory
+ }
+ TableViewColumn {
+ role: "fileSize"
+ title: "Size"
+ width: fileTableView.width - fileNameColumn.width - fileMofifiedColumn.width
+ movable: false
+ resizable: true
+ visible: !selectDirectory
+ }
+
+ function navigateToRow(row) {
+ currentRow = row;
+ navigateToCurrentRow();
+ }
+
+ function navigateToCurrentRow() {
+ var row = fileTableView.currentRow
+ var isFolder = model.isFolder(row);
+ var file = model.get(row).filePath;
+ if (isFolder) {
+ fileTableView.model.folder = helper.pathToUrl(file);
+ } else {
+ okAction.trigger();
+ }
+ }
+
+ property string prefix: ""
+
+ function addToPrefix(event) {
+ if (!event.text || event.text === "") {
+ return false;
+ }
+ var newPrefix = prefix + event.text.toLowerCase();
+ var matchedIndex = -1;
+ for (var i = 0; i < model.count; ++i) {
+ var name = model.get(i).fileName.toLowerCase();
+ if (0 === name.indexOf(newPrefix)) {
+ matchedIndex = i;
+ break;
+ }
+ }
+
+ if (matchedIndex !== -1) {
+ fileTableView.selection.clear();
+ fileTableView.selection.select(matchedIndex);
+ fileTableView.currentRow = matchedIndex;
+ fileTableView.prefix = newPrefix;
+ }
+ prefixClearTimer.restart();
+ return true;
+ }
+
+ Timer {
+ id: prefixClearTimer
+ interval: 1000
+ repeat: false
+ running: false
+ onTriggered: fileTableView.prefix = "";
+ }
+
+ Keys.onPressed: {
+ switch (event.key) {
+ case Qt.Key_Backspace:
+ case Qt.Key_Tab:
+ case Qt.Key_Backtab:
+ event.accepted = false;
+ break;
+
+ default:
+ if (addToPrefix(event)) {
+ event.accepted = true
+ } else {
+ event.accepted = false;
+ }
+ break;
+ }
+ }
+ }
+
+ TextField {
+ id: currentSelection
+ label: selectDirectory ? "Directory:" : "File name:"
+ anchors {
+ left: parent.left
+ right: selectionType.visible ? selectionType.left: parent.right
+ rightMargin: hifi.dimensions.contentSpacing.x
+ leftMargin: hifi.dimensions.contentSpacing.x
+ bottom: keyboard.top
+ bottomMargin: hifi.dimensions.contentSpacing.y
+ }
+ readOnly: !root.saveDialog
+ activeFocusOnTab: !readOnly
+ onActiveFocusChanged: if (activeFocus) { selectAll(); }
+ onAccepted: okAction.trigger();
+ }
+
+ FileTypeSelection {
+ id: selectionType
+ anchors {
+ top: currentSelection.top
+ left: buttonRow.left
+ right: parent.right
+ }
+ visible: !selectDirectory && filtersCount > 1
+ KeyNavigation.left: fileTableView
+ KeyNavigation.right: openButton
+ }
+
+ Keyboard {
+ id: keyboard
+ raised: parent.keyboardEnabled && parent.keyboardRaised
+ numeric: parent.punctuationMode
+ anchors {
+ left: parent.left
+ right: parent.right
+ bottom: buttonRow.top
+ bottomMargin: visible ? hifi.dimensions.contentSpacing.y : 0
+ }
+ }
+
+ Row {
+ id: buttonRow
+ anchors {
+ right: parent.right
+ bottom: parent.bottom
+ }
+ spacing: hifi.dimensions.contentSpacing.y
+
+ Button {
+ id: openButton
+ color: hifi.buttons.blue
+ action: okAction
+ Keys.onReturnPressed: okAction.trigger()
+ KeyNavigation.up: selectionType
+ KeyNavigation.left: selectionType
+ KeyNavigation.right: cancelButton
+ }
+
+ Button {
+ id: cancelButton
+ action: cancelAction
+ KeyNavigation.up: selectionType
+ KeyNavigation.left: openButton
+ KeyNavigation.right: fileTableView.contentItem
+ Keys.onReturnPressed: { canceled(); root.enabled = false }
+ }
+ }
+
+ Action {
+ id: okAction
+ text: currentSelection.text ? (root.selectDirectory && fileTableView.currentRow === -1 ? "Choose" : (root.saveDialog ? "Save" : "Open")) : "Open"
+ enabled: currentSelection.text || !root.selectDirectory && d.currentSelectionIsFolder ? true : false
+ onTriggered: {
+ if (!root.selectDirectory && !d.currentSelectionIsFolder
+ || root.selectDirectory && fileTableView.currentRow === -1) {
+ okActionTimer.start();
+ } else {
+ fileTableView.navigateToCurrentRow();
+ }
+ }
+ }
+
+ Timer {
+ id: okActionTimer
+ interval: 50
+ running: false
+ repeat: false
+ onTriggered: {
+ if (!root.saveDialog) {
+ selectedFile(d.currentSelectionUrl);
+ root.destroy();
+ return;
+ }
+
+ // Handle the ambiguity between different cases
+ // * typed name (with or without extension)
+ // * full path vs relative vs filename only
+ var selection = helper.saveHelper(currentSelection.text, root.dir, selectionType.currentFilter);
+
+ if (!selection) {
+ desktop.messageBox({ icon: OriginalDialogs.StandardIcon.Warning, text: "Unable to parse selection" })
+ return;
+ }
+
+ if (helper.urlIsDir(selection)) {
+ root.dir = selection;
+ currentSelection.text = "";
+ return;
+ }
+
+ // Check if the file is a valid target
+ if (!helper.urlIsWritable(selection)) {
+ desktop.messageBox({
+ icon: OriginalDialogs.StandardIcon.Warning,
+ text: "Unable to write to location " + selection
+ })
+ return;
+ }
+
+ if (helper.urlExists(selection)) {
+ var messageBox = desktop.messageBox({
+ icon: OriginalDialogs.StandardIcon.Question,
+ buttons: OriginalDialogs.StandardButton.Yes | OriginalDialogs.StandardButton.No,
+ text: "Do you wish to overwrite " + selection + "?",
+ });
+ var result = messageBox.exec();
+ if (OriginalDialogs.StandardButton.Yes !== result) {
+ return;
+ }
+ }
+
+ console.log("Selecting " + selection)
+ selectedFile(selection);
+ root.destroy();
+ }
+ }
+
+ Action {
+ id: cancelAction
+ text: "Cancel"
+ onTriggered: { canceled();root.destroy(); }
+ }
+ }
+
+ Keys.onPressed: {
+ switch (event.key) {
+ case Qt.Key_Backspace:
+ event.accepted = d.navigateUp();
+ break;
+
+ case Qt.Key_Home:
+ event.accepted = d.navigateHome();
+ break;
+
+ }
+ }
+}
diff --git a/interface/resources/qml/dialogs/TabletMessageBox.qml b/interface/resources/qml/dialogs/TabletMessageBox.qml
new file mode 100644
index 0000000000..f8876b1ec8
--- /dev/null
+++ b/interface/resources/qml/dialogs/TabletMessageBox.qml
@@ -0,0 +1,249 @@
+//
+// MessageDialog.qml
+//
+// Created by Bradley Austin Davis on 15 Jan 2016
+// Copyright 2016 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+import QtQuick 2.5
+import QtQuick.Controls 1.4
+import QtQuick.Dialogs 1.2 as OriginalDialogs
+
+import "../controls-uit"
+import "../styles-uit"
+import "../windows"
+
+import "messageDialog"
+
+TabletModalWindow {
+ id: root
+ HifiConstants { id: hifi }
+ visible: true
+
+ signal selected(int button);
+
+ MouseArea {
+ id: mouse;
+ anchors.fill: parent
+ }
+
+ function click(button) {
+ clickedButton = button;
+ selected(button);
+ destroy();
+ }
+
+ function exec() {
+ return OffscreenUi.waitForMessageBoxResult(root);
+ }
+
+ property alias detailedText: detailedText.text
+ property alias text: mainTextContainer.text
+ property alias informativeText: informativeTextContainer.text
+ property int buttons: OriginalDialogs.StandardButton.Ok
+ property int icon: OriginalDialogs.StandardIcon.NoIcon
+ property string iconText: ""
+ property int iconSize: 50
+ onIconChanged: updateIcon();
+ property int defaultButton: OriginalDialogs.StandardButton.NoButton;
+ property int clickedButton: OriginalDialogs.StandardButton.NoButton;
+ focus: defaultButton === OriginalDialogs.StandardButton.NoButton
+
+ property int titleWidth: 0
+ onTitleWidthChanged: d.resize();
+
+ function updateIcon() {
+ if (!root) {
+ return;
+ }
+ iconText = hifi.glyphForIcon(root.icon);
+ }
+
+ TabletModalFrame {
+ id: messageBox
+ clip: true
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.horizontalCenter: parent.horizontalCenter
+ width: parent.width - 6
+ height: 300
+
+ QtObject {
+ id: d
+ readonly property int minWidth: 200
+ readonly property int maxWidth: 1280
+ readonly property int minHeight: 120
+ readonly property int maxHeight: 720
+
+ function resize() {
+ var targetWidth = Math.max(titleWidth, mainTextContainer.contentWidth)
+ var targetHeight = mainTextContainer.height + 3 * hifi.dimensions.contentSpacing.y
+ + (informativeTextContainer.text != "" ? informativeTextContainer.contentHeight + 3 * hifi.dimensions.contentSpacing.y : 0)
+ + buttons.height
+ + (details.implicitHeight + hifi.dimensions.contentSpacing.y) + messageBox.frameMarginTop
+ messageBox.height = (targetHeight < d.minHeight) ? d.minHeight: ((targetHeight > d.maxHeight) ? d.maxHeight : targetHeight)
+ }
+ }
+
+ RalewaySemiBold {
+ id: mainTextContainer
+ onTextChanged: d.resize();
+ wrapMode: Text.WordWrap
+ size: hifi.fontSizes.sectionName
+ color: hifi.colors.baseGrayHighlight
+ width: parent.width - 6
+ anchors {
+ top: parent.top
+ horizontalCenter: parent.horizontalCenter
+ margins: 0
+ topMargin: hifi.dimensions.contentSpacing.y + messageBox.frameMarginTop
+ }
+ maximumLineCount: 30
+ elide: Text.ElideLeft
+ lineHeight: 2
+ lineHeightMode: Text.ProportionalHeight
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ RalewaySemiBold {
+ id: informativeTextContainer
+ onTextChanged: d.resize();
+ wrapMode: Text.WordWrap
+ size: hifi.fontSizes.sectionName
+ color: hifi.colors.baseGrayHighlight
+ anchors {
+ top: mainTextContainer.bottom
+ left: parent.left
+ right: parent.right
+ margins: 0
+ topMargin: text != "" ? hifi.dimensions.contentSpacing.y : 0
+ }
+ }
+
+ Flow {
+ id: buttons
+ focus: true
+ spacing: hifi.dimensions.contentSpacing.x
+ onHeightChanged: d.resize(); onWidthChanged: d.resize();
+ layoutDirection: Qt.RightToLeft
+ anchors {
+ top: informativeTextContainer.text == "" ? mainTextContainer.bottom : informativeTextContainer.bottom
+ horizontalCenter: parent.horizontalCenter
+ margins: 0
+ topMargin: 2 * hifi.dimensions.contentSpacing.y
+ }
+ MessageDialogButton { dialog: root; text: qsTr("Close"); button: OriginalDialogs.StandardButton.Close; }
+ MessageDialogButton { dialog: root; text: qsTr("Abort"); button: OriginalDialogs.StandardButton.Abort; }
+ MessageDialogButton { dialog: root; text: qsTr("Cancel"); button: OriginalDialogs.StandardButton.Cancel; }
+ MessageDialogButton { dialog: root; text: qsTr("Restore Defaults"); button: OriginalDialogs.StandardButton.RestoreDefaults; }
+ MessageDialogButton { dialog: root; text: qsTr("Reset"); button: OriginalDialogs.StandardButton.Reset; }
+ MessageDialogButton { dialog: root; text: qsTr("Discard"); button: OriginalDialogs.StandardButton.Discard; }
+ MessageDialogButton { dialog: root; text: qsTr("No to All"); button: OriginalDialogs.StandardButton.NoToAll; }
+ MessageDialogButton { dialog: root; text: qsTr("No"); button: OriginalDialogs.StandardButton.No; }
+ MessageDialogButton { dialog: root; text: qsTr("Yes to All"); button: OriginalDialogs.StandardButton.YesToAll; }
+ MessageDialogButton { dialog: root; text: qsTr("Yes"); button: OriginalDialogs.StandardButton.Yes; }
+ MessageDialogButton { dialog: root; text: qsTr("Apply"); button: OriginalDialogs.StandardButton.Apply; }
+ MessageDialogButton { dialog: root; text: qsTr("Ignore"); button: OriginalDialogs.StandardButton.Ignore; }
+ MessageDialogButton { dialog: root; text: qsTr("Retry"); button: OriginalDialogs.StandardButton.Retry; }
+ MessageDialogButton { dialog: root; text: qsTr("Save All"); button: OriginalDialogs.StandardButton.SaveAll; }
+ MessageDialogButton { dialog: root; text: qsTr("Save"); button: OriginalDialogs.StandardButton.Save; }
+ MessageDialogButton { dialog: root; text: qsTr("Open"); button: OriginalDialogs.StandardButton.Open; }
+ MessageDialogButton { dialog: root; text: qsTr("OK"); button: OriginalDialogs.StandardButton.Ok; }
+
+ Button {
+ id: moreButton
+ text: qsTr("Show Details...")
+ width: 160
+ onClicked: { content.state = (content.state === "" ? "expanded" : "") }
+ visible: detailedText && detailedText.length > 0
+ }
+ MessageDialogButton { dialog: root; text: qsTr("Help"); button: OriginalDialogs.StandardButton.Help; }
+ }
+
+ Item {
+ id: details
+ width: parent.width
+ implicitHeight: detailedText.implicitHeight
+ height: 0
+ clip: true
+ anchors {
+ top: buttons.bottom
+ left: parent.left;
+ right: parent.right;
+ margins: 0
+ topMargin: hifi.dimensions.contentSpacing.y
+ }
+ Flickable {
+ id: flickable
+ contentHeight: detailedText.height
+ anchors.fill: parent
+ anchors.topMargin: hifi.dimensions.contentSpacing.x
+ anchors.bottomMargin: hifi.dimensions.contentSpacing.y
+ TextEdit {
+ id: detailedText
+ size: hifi.fontSizes.menuItem
+ color: hifi.colors.baseGrayHighlight
+ width: details.width
+ wrapMode: Text.WordWrap
+ readOnly: true
+ selectByMouse: true
+ anchors.margins: 0
+ }
+ }
+ }
+
+ states: [
+ State {
+ name: "expanded"
+ PropertyChanges { target: root; anchors.fill: undefined }
+ PropertyChanges { target: details; height: 120 }
+ PropertyChanges { target: moreButton; text: qsTr("Hide Details") }
+ }
+ ]
+
+ Component.onCompleted: {
+ updateIcon();
+ d.resize();
+ }
+ onStateChanged: d.resize()
+ }
+
+ Keys.onPressed: {
+ if (!visible) {
+ return
+ }
+
+ if (event.modifiers === Qt.ControlModifier)
+ switch (event.key) {
+ case Qt.Key_A:
+ event.accepted = true
+ detailedText.selectAll()
+ break
+ case Qt.Key_C:
+ event.accepted = true
+ detailedText.copy()
+ break
+ case Qt.Key_Period:
+ if (Qt.platform.os === "osx") {
+ event.accepted = true
+ content.reject()
+ }
+ break
+ } else switch (event.key) {
+ case Qt.Key_Escape:
+ case Qt.Key_Back:
+ event.accepted = true
+ root.click(OriginalDialogs.StandardButton.Cancel)
+ break
+
+ case Qt.Key_Enter:
+ case Qt.Key_Return:
+ event.accepted = true
+ root.click(root.defaultButton)
+ break
+ }
+ }
+}
diff --git a/interface/resources/qml/dialogs/TabletQueryDialog.qml b/interface/resources/qml/dialogs/TabletQueryDialog.qml
new file mode 100644
index 0000000000..3ff3347ebc
--- /dev/null
+++ b/interface/resources/qml/dialogs/TabletQueryDialog.qml
@@ -0,0 +1,206 @@
+//
+// QueryDialog.qml
+//
+// Created by Bradley Austin Davis on 22 Jan 2016
+// Copyright 2015 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+import QtQuick 2.5
+import QtQuick.Controls 1.4
+import QtQuick.Dialogs 1.2 as OriginalDialogs
+
+import "../controls-uit"
+import "../styles-uit"
+import "../windows"
+
+TabletModalWindow {
+ id: root
+ HifiConstants { id: hifi }
+ signal selected(var result);
+ signal canceled();
+ layer.enabled: true
+ property int icon: hifi.icons.none
+ property string iconText: ""
+ property int iconSize: 35
+
+ MouseArea {
+ width: parent.width
+ height: parent.height
+ }
+
+ property bool keyboardOverride: true
+ onIconChanged: updateIcon();
+
+ property var items;
+ property string label: ""
+ property var result;
+ property alias current: textResult.text
+
+ // For text boxes
+ property alias placeholderText: textResult.placeholderText
+
+ // For combo boxes
+ property bool editable: true;
+
+ property int titleWidth: 0
+ onTitleWidthChanged: d.resize();
+
+ property bool keyboardEnabled: false
+ property bool keyboardRaised: false
+ property bool punctuationMode: false
+
+ onKeyboardRaisedChanged: d.resize();
+
+ function updateIcon() {
+ if (!root) {
+ return;
+ }
+ iconText = hifi.glyphForIcon(root.icon);
+ }
+
+ 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: 120
+ readonly property int maxHeight: 720
+
+ function resize() {
+ var targetWidth = Math.max(titleWidth, 470)
+ var targetHeight = (items ? comboBox.controlHeight : textResult.controlHeight) + 5 * hifi.dimensions.contentSpacing.y + buttons.height
+ modalWindowItem.width = (targetWidth < d.minWidth) ? d.minWidth : ((targetWidth > d.maxWdith) ? d.maxWidth : targetWidth);
+ modalWindowItem.height = ((targetHeight < d.minHeight) ? d.minHeight : ((targetHeight > d.maxHeight) ? d.maxHeight : targetHeight)) + ((keyboardEnabled && keyboardRaised) ? (keyboard.raisedHeight + 2 * hifi.dimensions.contentSpacing.y) : 0) + modalWindowItem.frameMarginTop
+ }
+ }
+
+ Item {
+ anchors {
+ top: parent.top
+ bottom: keyboard.top;
+ left: parent.left;
+ right: parent.right;
+ margins: 0
+ bottomMargin: 2 * hifi.dimensions.contentSpacing.y
+ }
+
+ // FIXME make a text field type that can be bound to a history for autocompletion
+ TextField {
+ id: textResult
+ label: root.label
+ focus: items ? false : true
+ visible: items ? false : true
+ anchors {
+ left: parent.left;
+ right: parent.right;
+ bottom: parent.bottom
+ leftMargin: 5
+ }
+ }
+
+ TabletComboBox {
+ id: comboBox
+ label: root.label
+ focus: true
+ visible: items ? true : false
+ anchors {
+ left: parent.left
+ right: parent.right
+ bottom: parent.bottom
+ rightMargin: 5
+ }
+ model: items ? items : []
+ }
+ }
+
+ property alias keyboardOverride: root.keyboardOverride
+ property alias keyboardRaised: root.keyboardRaised
+ property alias punctuationMode: root.punctuationMode
+
+ Keyboard {
+ id: keyboard
+ raised: keyboardEnabled && keyboardRaised
+ numeric: punctuationMode
+ anchors {
+ left: parent.left
+ right: parent.right
+ bottom: buttons.top
+ bottomMargin: raised ? 2 * hifi.dimensions.contentSpacing.y : 0
+ }
+ }
+
+ Flow {
+ id: buttons
+ focus: true
+ spacing: hifi.dimensions.contentSpacing.x
+ onHeightChanged: d.resize(); onWidthChanged: d.resize();
+ layoutDirection: Qt.RightToLeft
+ anchors {
+ bottom: parent.bottom
+ right: parent.right
+ margins: 0
+ bottomMargin: hifi.dimensions.contentSpacing.y
+ }
+ Button { action: cancelAction }
+ Button { action: acceptAction }
+ }
+
+ Action {
+ id: cancelAction
+ text: qsTr("Cancel")
+ shortcut: Qt.Key_Escape
+ onTriggered: {
+ root.canceled();
+ root.destroy();
+ }
+ }
+ Action {
+ id: acceptAction
+ text: qsTr("OK")
+ shortcut: Qt.Key_Return
+ onTriggered: {
+ root.result = items ? comboBox.currentText : textResult.text
+ 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();
+ d.resize();
+ textResult.forceActiveFocus();
+ }
+}
diff --git a/interface/resources/qml/dialogs/preferences/AvatarPreference.qml b/interface/resources/qml/dialogs/preferences/AvatarPreference.qml
index 0c5c5bf630..53d36b6c79 100644
--- a/interface/resources/qml/dialogs/preferences/AvatarPreference.qml
+++ b/interface/resources/qml/dialogs/preferences/AvatarPreference.qml
@@ -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);
}
diff --git a/interface/resources/qml/hifi/dialogs/GeneralPreferencesDialog.qml b/interface/resources/qml/hifi/dialogs/GeneralPreferencesDialog.qml
index 9be1c30e55..44cae95696 100644
--- a/interface/resources/qml/hifi/dialogs/GeneralPreferencesDialog.qml
+++ b/interface/resources/qml/hifi/dialogs/GeneralPreferencesDialog.qml
@@ -1,5 +1,5 @@
//
-// PreferencesDialog.qml
+// GeneralPreferencesDialog.qml
//
// Created by Bradley Austin Davis on 24 Jan 2016
// Copyright 2015 High Fidelity, Inc.
diff --git a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml
new file mode 100644
index 0000000000..93e18c7aba
--- /dev/null
+++ b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml
@@ -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
+ }
+ }
+ }
+ }
+ }
+}
+
diff --git a/interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml b/interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml
new file mode 100644
index 0000000000..e217a6e38e
--- /dev/null
+++ b/interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml
@@ -0,0 +1,344 @@
+//
+// RunningScripts.qml
+//
+// Created by Bradley Austin Davis on 12 Jan 2016
+// Copyright 2016 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+import QtQuick 2.5
+import QtQuick.Controls 1.4
+import QtQuick.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: "RunningScripts"
+ HifiConstants { id: hifi }
+ signal sendToScript(var message);
+ property var eventBridge;
+ property var scripts: ScriptDiscoveryService;
+ property var scriptsModel: scripts.scriptsModelFilter
+ property var runningScriptsModel: ListModel { }
+ property bool isHMD: false
+
+ color: hifi.colors.baseGray
+
+ Connections {
+ target: ScriptDiscoveryService
+ onScriptCountChanged: updateRunningScripts();
+ }
+
+ Component.onCompleted: {
+ isHMD = HMD.active;
+ updateRunningScripts();
+ }
+
+ function updateRunningScripts() {
+ var runningScripts = ScriptDiscoveryService.getRunning();
+ runningScriptsModel.clear()
+ for (var i = 0; i < runningScripts.length; ++i) {
+ runningScriptsModel.append(runningScripts[i]);
+ }
+ }
+
+ function loadScript(script) {
+ console.log("Load script " + script);
+ scripts.loadOneScript(script);
+ }
+
+ function reloadScript(script) {
+ console.log("Reload script " + script);
+ scripts.stopScript(script, true);
+ }
+
+ function stopScript(script) {
+ console.log("Stop script " + script);
+ scripts.stopScript(script);
+ }
+
+ function reloadAll() {
+ console.log("Reload all scripts");
+ scripts.reloadAllScripts();
+ }
+
+ function loadDefaults() {
+ console.log("Load default scripts");
+ scripts.loadOneScript(scripts.defaultScriptsPath + "/defaultScripts.js");
+ }
+
+ function stopAll() {
+ console.log("Stop all scripts");
+ scripts.stopAllScripts();
+ }
+
+ Column {
+ width: parent.width
+ HifiControls.TabletContentSection {
+ name: "Currently Running"
+ isFirst: true
+
+ HifiControls.VerticalSpacer {}
+
+ Row {
+ spacing: hifi.dimensions.contentSpacing.x
+
+ HifiControls.Button {
+ text: "Reload All"
+ color: hifi.buttons.black
+ onClicked: reloadAll()
+ }
+
+ HifiControls.Button {
+ text: "Remove All"
+ color: hifi.buttons.red
+ onClicked: stopAll()
+ }
+ }
+
+ HifiControls.VerticalSpacer {
+ height: hifi.dimensions.controlInterlineHeight + 2 // Add space for border
+ }
+
+ HifiControls.Table {
+ model: runningScriptsModel
+ id: table
+ height: 185
+ colorScheme: hifi.colorSchemes.dark
+ anchors.left: parent.left
+ anchors.right: parent.right
+ expandSelectedRow: true
+
+ itemDelegate: Item {
+ anchors {
+ left: parent ? parent.left : undefined
+ leftMargin: hifi.dimensions.tablePadding
+ right: parent ? parent.right : undefined
+ rightMargin: hifi.dimensions.tablePadding
+ }
+
+ FiraSansSemiBold {
+ id: textItem
+ text: styleData.value
+ size: hifi.fontSizes.tableText
+ color: table.colorScheme == hifi.colorSchemes.light
+ ? (styleData.selected ? hifi.colors.black : hifi.colors.baseGrayHighlight)
+ : (styleData.selected ? hifi.colors.black : hifi.colors.lightGrayText)
+ anchors {
+ left: parent.left
+ right: parent.right
+ top: parent.top
+ topMargin: 3
+ }
+
+ HiFiGlyphs {
+ id: reloadButton
+ text: hifi.glyphs.reloadSmall
+ color: reloadButtonArea.pressed ? hifi.colors.white : parent.color
+ anchors {
+ top: parent.top
+ right: stopButton.left
+ verticalCenter: parent.verticalCenter
+ }
+ MouseArea {
+ id: reloadButtonArea
+ anchors { fill: parent; margins: -2 }
+ onClicked: reloadScript(model.url)
+ }
+ }
+
+ HiFiGlyphs {
+ id: stopButton
+ text: hifi.glyphs.closeSmall
+ color: stopButtonArea.pressed ? hifi.colors.white : parent.color
+ anchors {
+ top: parent.top
+ right: parent.right
+ verticalCenter: parent.verticalCenter
+ }
+ MouseArea {
+ id: stopButtonArea
+ anchors { fill: parent; margins: -2 }
+ onClicked: stopScript(model.url)
+ }
+ }
+
+ }
+
+ FiraSansSemiBold {
+ text: runningScriptsModel.get(styleData.row) ? runningScriptsModel.get(styleData.row).url : ""
+ elide: Text.ElideMiddle
+ size: hifi.fontSizes.tableText
+ color: table.colorScheme == hifi.colorSchemes.light
+ ? (styleData.selected ? hifi.colors.black : hifi.colors.lightGray)
+ : (styleData.selected ? hifi.colors.black : hifi.colors.lightGrayText)
+ anchors {
+ top: textItem.bottom
+ left: parent.left
+ right: parent.right
+ }
+ visible: styleData.selected
+ }
+ }
+
+ TableViewColumn {
+ role: "name"
+ }
+ }
+
+ HifiControls.VerticalSpacer {
+ height: hifi.dimensions.controlInterlineHeight + 2 // Add space for border
+ }
+ }
+
+ HifiControls.TabletContentSection {
+ name: "Load Scripts"
+
+ HifiControls.VerticalSpacer {}
+
+ Row {
+ spacing: hifi.dimensions.contentSpacing.x
+
+ HifiControls.Button {
+ text: "from URL"
+ color: hifi.buttons.black
+ height: 26
+ onClicked: fromUrlTimer.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: fromUrlTimer
+ interval: 5
+ repeat: false
+ running: false
+ onTriggered: ApplicationInterface.loadScriptURLDialog();
+ }
+ }
+
+ HifiControls.Button {
+ text: "from Disk"
+ color: hifi.buttons.black
+ height: 26
+ onClicked: fromDiskTimer.running = true
+
+ Timer {
+ id: fromDiskTimer
+ interval: 5
+ repeat: false
+ running: false
+ onTriggered: ApplicationInterface.loadDialog();
+ }
+ }
+
+ HifiControls.Button {
+ text: "Load Defaults"
+ color: hifi.buttons.black
+ height: 26
+ onClicked: loadDefaults()
+ }
+ }
+
+ HifiControls.VerticalSpacer {}
+
+ HifiControls.TextField {
+ id: filterEdit
+ isSearchField: true
+ anchors.left: parent.left
+ anchors.right: parent.right
+ colorScheme: hifi.colorSchemes.dark
+ placeholderText: "Filter"
+ onTextChanged: scriptsModel.filterRegExp = new RegExp("^.*" + text + ".*$", "i")
+ Component.onCompleted: scriptsModel.filterRegExp = new RegExp("^.*$", "i")
+ }
+
+ HifiControls.VerticalSpacer {
+ height: hifi.dimensions.controlInterlineHeight + 2 // Add space for border
+ }
+
+ HifiControls.Tree {
+ id: treeView
+ height: 155
+ treeModel: scriptsModel
+ colorScheme: hifi.colorSchemes.dark
+ anchors.left: parent.left
+ anchors.right: parent.right
+ }
+
+ HifiControls.VerticalSpacer {
+ height: hifi.dimensions.controlInterlineHeight + 2 // Add space for border
+ }
+
+ HifiControls.TextField {
+ id: selectedScript
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.rightMargin: loadButton.width + hifi.dimensions.contentSpacing.x
+
+ colorScheme: hifi.colorSchemes.dark
+ readOnly: true
+
+ Connections {
+ target: treeView
+ onCurrentIndexChanged: {
+ var path = scriptsModel.data(treeView.currentIndex, 0x100)
+ if (path) {
+ selectedScript.text = path
+ } else {
+ selectedScript.text = ""
+ }
+ }
+ }
+ }
+
+ Item {
+ // Take the loadButton out of the column flow.
+ id: loadButtonContainer
+ anchors.top: selectedScript.top
+ anchors.right: parent.right
+
+ HifiControls.Button {
+ id: loadButton
+ anchors.right: parent.right
+
+ text: "Load"
+ color: hifi.buttons.blue
+ enabled: selectedScript.text != ""
+ onClicked: root.loadScript(selectedScript.text)
+ }
+ }
+
+ HifiControls.VerticalSpacer {
+ height: hifi.dimensions.controlInterlineHeight - (!isHMD ? 3 : 0)
+ }
+
+ HifiControls.TextAction {
+ id: directoryButton
+ icon: hifi.glyphs.script
+ iconSize: 24
+ text: "Reveal Scripts Folder"
+ onClicked: fileDialogHelper.openDirectory(scripts.defaultScriptsPath)
+ colorScheme: hifi.colorSchemes.dark
+ anchors.left: parent.left
+ visible: !isHMD
+ }
+
+ HifiControls.VerticalSpacer {
+ height: hifi.dimensions.controlInterlineHeight - 3
+ visible: !isHMD
+ }
+ }
+ }
+}
+
diff --git a/interface/resources/qml/hifi/tablet/Edit.qml b/interface/resources/qml/hifi/tablet/Edit.qml
new file mode 100644
index 0000000000..00856f8212
--- /dev/null
+++ b/interface/resources/qml/hifi/tablet/Edit.qml
@@ -0,0 +1,354 @@
+import QtQuick 2.5
+import QtQuick.Controls 1.4
+import QtWebEngine 1.1
+import QtWebChannel 1.0
+import QtQuick.Controls.Styles 1.4
+import "../../controls"
+import "../toolbars"
+import HFWebEngineProfile 1.0
+import QtGraphicalEffects 1.0
+import "../../styles-uit"
+
+StackView {
+ id: editRoot
+ objectName: "stack"
+ initialItem: editBasePage
+
+ property var eventBridge;
+ signal sendToScript(var message);
+
+ function pushSource(path) {
+ editRoot.push(Qt.resolvedUrl(path));
+ editRoot.currentItem.eventBridge = editRoot.eventBridge;
+ editRoot.currentItem.sendToScript.connect(editRoot.sendToScript);
+ }
+
+ function popSource() {
+ editRoot.pop();
+ }
+
+
+ Component {
+ id: editBasePage
+ TabView {
+ id: editTabView
+ // anchors.fill: parent
+ height: 60
+
+ Tab {
+ title: "CREATE"
+ active: true
+ enabled: true
+ property string originalUrl: ""
+
+ Rectangle {
+ color: "#404040"
+
+ Text {
+ color: "#ffffff"
+ text: "Choose an Entity Type to Create:"
+ font.pixelSize: 14
+ font.bold: true
+ anchors.top: parent.top
+ anchors.topMargin: 28
+ anchors.left: parent.left
+ anchors.leftMargin: 28
+ }
+
+ Flow {
+ id: createEntitiesFlow
+ spacing: 35
+ anchors.right: parent.right
+ anchors.rightMargin: 55
+ anchors.left: parent.left
+ anchors.leftMargin: 55
+ anchors.top: parent.top
+ anchors.topMargin: 70
+
+
+ NewEntityButton {
+ icon: "icons/create-icons/94-model-01.svg"
+ text: "MODEL"
+ onClicked: {
+ editRoot.sendToScript({
+ method: "newEntityButtonClicked", params: { buttonName: "newModelButton" }
+ });
+ editTabView.currentIndex = 2
+ }
+ }
+
+ NewEntityButton {
+ icon: "icons/create-icons/21-cube-01.svg"
+ text: "CUBE"
+ onClicked: {
+ editRoot.sendToScript({
+ method: "newEntityButtonClicked", params: { buttonName: "newCubeButton" }
+ });
+ editTabView.currentIndex = 2
+ }
+ }
+
+ NewEntityButton {
+ icon: "icons/create-icons/22-sphere-01.svg"
+ text: "SPHERE"
+ onClicked: {
+ editRoot.sendToScript({
+ method: "newEntityButtonClicked", params: { buttonName: "newSphereButton" }
+ });
+ editTabView.currentIndex = 2
+ }
+ }
+
+ NewEntityButton {
+ icon: "icons/create-icons/24-light-01.svg"
+ text: "LIGHT"
+ onClicked: {
+ editRoot.sendToScript({
+ method: "newEntityButtonClicked", params: { buttonName: "newLightButton" }
+ });
+ editTabView.currentIndex = 2
+ }
+ }
+
+ NewEntityButton {
+ icon: "icons/create-icons/20-text-01.svg"
+ text: "TEXT"
+ onClicked: {
+ editRoot.sendToScript({
+ method: "newEntityButtonClicked", params: { buttonName: "newTextButton" }
+ });
+ editTabView.currentIndex = 2
+ }
+ }
+
+ NewEntityButton {
+ icon: "icons/create-icons/25-web-1-01.svg"
+ text: "WEB"
+ onClicked: {
+ editRoot.sendToScript({
+ method: "newEntityButtonClicked", params: { buttonName: "newWebButton" }
+ });
+ editTabView.currentIndex = 2
+ }
+ }
+
+ NewEntityButton {
+ icon: "icons/create-icons/23-zone-01.svg"
+ text: "ZONE"
+ onClicked: {
+ editRoot.sendToScript({
+ method: "newEntityButtonClicked", params: { buttonName: "newZoneButton" }
+ });
+ editTabView.currentIndex = 2
+ }
+ }
+
+ NewEntityButton {
+ icon: "icons/create-icons/90-particles-01.svg"
+ text: "PARTICLE"
+ onClicked: {
+ editRoot.sendToScript({
+ method: "newEntityButtonClicked", params: { buttonName: "newParticleButton" }
+ });
+ editTabView.currentIndex = 2
+ }
+ }
+ }
+
+ Item {
+ id: assetServerButton
+ width: 370
+ height: 38
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.top: createEntitiesFlow.bottom
+ anchors.topMargin: 35
+
+ Rectangle {
+ id: assetServerButtonBg
+ color: "black"
+ opacity: 1
+ radius: 6
+ 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: assetServerButtonGradient
+ gradient: Gradient {
+ GradientStop {
+ position: 0
+ color: "#383838"
+ }
+
+ GradientStop {
+ position: 1
+ color: "black"
+ }
+ }
+ opacity: 1
+ radius: 6
+ 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
+ }
+
+ Text {
+ color: "#ffffff"
+ text: "OPEN THIS DOMAIN'S ASSET SERVER"
+ font.bold: true
+ font.pixelSize: 14
+ anchors.centerIn: parent
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ hoverEnabled: true
+ enabled: true
+ onClicked: {
+ editRoot.sendToScript({
+ method: "newEntityButtonClicked", params: { buttonName: "openAssetBrowserButton" }
+ });
+ }
+ onEntered: {
+ assetServerButton.state = "hover state";
+ }
+ onExited: {
+ assetServerButton.state = "base state";
+ }
+ }
+
+ states: [
+ State {
+ name: "hover state"
+
+ PropertyChanges {
+ target: assetServerButtonGradient
+ opacity: 0
+ }
+ },
+ State {
+ name: "base state"
+
+ PropertyChanges {
+ target: assetServerButtonGradient
+ opacity: 1
+ }
+ }
+ ]
+ }
+ }
+ }
+
+ Tab {
+ title: "LIST"
+ active: true
+ enabled: true
+ property string originalUrl: ""
+
+ WebView {
+ id: entityListToolWebView
+ url: "../../../../../scripts/system/html/entityList.html"
+ eventBridge: editRoot.eventBridge
+ anchors.fill: parent
+ enabled: true
+ }
+ }
+
+ Tab {
+ title: "PROPERTIES"
+ active: true
+ enabled: true
+ property string originalUrl: ""
+
+ WebView {
+ id: entityPropertiesWebView
+ url: "../../../../../scripts/system/html/entityProperties.html"
+ eventBridge: editRoot.eventBridge
+ anchors.fill: parent
+ enabled: true
+ }
+ }
+
+ Tab {
+ title: "GRID"
+ active: true
+ enabled: true
+ property string originalUrl: ""
+
+ WebView {
+ id: gridControlsWebView
+ url: "../../../../../scripts/system/html/gridControls.html"
+ eventBridge: editRoot.eventBridge
+ anchors.fill: parent
+ enabled: true
+ }
+ }
+
+ Tab {
+ title: "P"
+ active: true
+ enabled: true
+ property string originalUrl: ""
+
+ WebView {
+ id: particleExplorerWebView
+ url: "../../../../../scripts/system/particle_explorer/particleExplorer.html"
+ eventBridge: editRoot.eventBridge
+ anchors.fill: parent
+ enabled: true
+ }
+ }
+
+
+ style: TabViewStyle {
+ frameOverlap: 1
+ tab: Rectangle {
+ color: styleData.selected ? "#404040" :"black"
+ implicitWidth: text.width + 42
+ implicitHeight: 40
+ Text {
+ id: text
+ anchors.centerIn: parent
+ text: styleData.title
+ font.pixelSize: 16
+ font.bold: true
+ color: styleData.selected ? "white" : "white"
+ property string glyphtext: ""
+ HiFiGlyphs {
+ anchors.centerIn: parent
+ size: 30
+ color: "#ffffff"
+ text: text.glyphtext
+ }
+ Component.onCompleted: if (styleData.title == "P") {
+ text.text = " ";
+ text.glyphtext = "\ue004";
+ }
+ }
+ }
+ tabBar: Rectangle {
+ color: "black"
+ 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
+ }
+ }
+ }
+ }
+}
diff --git a/interface/resources/qml/hifi/tablet/NewEntityButton.qml b/interface/resources/qml/hifi/tablet/NewEntityButton.qml
new file mode 100644
index 0000000000..e5684fa791
--- /dev/null
+++ b/interface/resources/qml/hifi/tablet/NewEntityButton.qml
@@ -0,0 +1,160 @@
+import QtQuick 2.0
+import QtGraphicalEffects 1.0
+
+Item {
+ id: newEntityButton
+ property var uuid;
+ property string text: "ENTITY"
+ property string icon: "icons/edit-icon.svg"
+ property string activeText: newEntityButton.text
+ property string activeIcon: newEntityButton.icon
+ property bool isActive: false
+ property bool inDebugMode: false
+ property bool isEntered: false
+ property double sortOrder: 100
+ property int stableOrder: 0
+ property var tabletRoot;
+ width: 100
+ height: 100
+
+ 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: "#1c1c1c"
+ opacity: 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
+ }
+
+ function urlHelper(src) {
+ if (src.match(/\bhttp/)) {
+ return src;
+ } else {
+ return "../../../" + src;
+ }
+ }
+
+ Rectangle {
+ id: buttonOutline
+ color: "#00000000"
+ opacity: 0
+ 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: newEntityButton.urlHelper(newEntityButton.icon)
+ }
+
+ ColorOverlay {
+ id: iconColorOverlay
+ anchors.fill: icon
+ source: icon
+ color: "#ffffff"
+ }
+
+ Text {
+ id: text
+ color: "#ffffff"
+ text: newEntityButton.text
+ font.bold: true
+ font.pixelSize: 16
+ anchors.bottom: parent.bottom
+ anchors.bottomMargin: 12
+ anchors.horizontalCenter: parent.horizontalCenter
+ horizontalAlignment: Text.AlignHCenter
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ hoverEnabled: true
+ enabled: true
+ onClicked: {
+ newEntityButton.clicked();
+ }
+ onEntered: {
+ newEntityButton.state = "hover state";
+ }
+ onExited: {
+ newEntityButton.state = "base state";
+ }
+ }
+
+ states: [
+ State {
+ name: "hover state"
+
+ PropertyChanges {
+ target: buttonOutline
+ opacity: 1
+ }
+
+ PropertyChanges {
+ target: glow
+ visible: true
+ }
+ },
+ State {
+ name: "base state"
+
+ PropertyChanges {
+ target: glow
+ visible: false
+ }
+ }
+ ]
+}
+
+
diff --git a/interface/resources/qml/hifi/tablet/NewModelDialog.qml b/interface/resources/qml/hifi/tablet/NewModelDialog.qml
new file mode 100644
index 0000000000..d57ffdc72f
--- /dev/null
+++ b/interface/resources/qml/hifi/tablet/NewModelDialog.qml
@@ -0,0 +1,158 @@
+//
+// NewModelDialog.qml
+// qml/hifi
+//
+// Created by Seth Alves on 2017-2-10
+// 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 "../../styles-uit"
+import "../../controls-uit"
+
+Rectangle {
+ id: newModelDialog
+ // width: parent.width
+ // height: parent.height
+ HifiConstants { id: hifi }
+ color: hifi.colors.baseGray;
+ property var eventBridge;
+ signal sendToScript(var message);
+
+ Column {
+ id: column1
+ anchors.rightMargin: 10
+ anchors.leftMargin: 10
+ anchors.bottomMargin: 10
+ anchors.topMargin: 10
+ anchors.fill: parent
+ spacing: 5
+
+ Text {
+ id: text1
+ text: qsTr("Model URL")
+ color: "#ffffff"
+ font.pixelSize: 12
+ }
+
+ TextInput {
+ id: modelURL
+ height: 20
+ text: qsTr("")
+ anchors.left: parent.left
+ anchors.leftMargin: 0
+ anchors.right: parent.right
+ anchors.rightMargin: 0
+ font.pixelSize: 12
+ }
+
+ Row {
+ id: row1
+ height: 400
+ spacing: 30
+ anchors.left: parent.left
+ anchors.leftMargin: 0
+ anchors.right: parent.right
+ anchors.rightMargin: 0
+
+ Column {
+ id: column2
+ width: 200
+ height: 400
+ spacing: 10
+
+ CheckBox {
+ id: dynamic
+ text: qsTr("Dynamic")
+
+ }
+
+ Row {
+ id: row2
+ width: 200
+ height: 400
+ spacing: 20
+
+ Image {
+ id: image1
+ width: 30
+ height: 30
+ source: "qrc:/qtquickplugin/images/template_image.png"
+ }
+
+ Text {
+ id: text2
+ width: 160
+ color: "#ffffff"
+ text: qsTr("Models with automatic collisions set to 'Exact' cannot be dynamic")
+ wrapMode: Text.WordWrap
+ font.pixelSize: 12
+ }
+ }
+ }
+
+ Column {
+ id: column3
+ height: 400
+ spacing: 10
+
+ Text {
+ id: text3
+ text: qsTr("Automatic Collisions")
+ color: "#ffffff"
+ font.pixelSize: 12
+ }
+
+ TabletComboBox {
+ id: collisionType
+ width: 200
+ z: 100
+ transformOrigin: Item.Center
+ model: ["No Collision",
+ "Basic - Whole model",
+ "Good - Sub-meshes",
+ "Exact - All polygons"]
+ }
+
+ Row {
+ id: row3
+ width: 200
+ height: 400
+ spacing: 5
+
+ anchors {
+ rightMargin: 15
+ }
+ Button {
+ id: button1
+ text: qsTr("Add")
+ z: -1
+ onClicked: {
+ newModelDialog.sendToScript({
+ method: "newModelDialogAdd",
+ params: {
+ textInput: modelURL.text,
+ checkBox: dynamic.checked,
+ comboBox: collisionType.currentIndex
+ }
+ });
+ }
+ }
+
+ Button {
+ id: button2
+ z: -1
+ text: qsTr("Cancel")
+ onClicked: {
+ newModelDialog.sendToScript({method: "newModelDialogCancel"})
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/interface/resources/qml/hifi/tablet/TabletAudioPreferences.qml b/interface/resources/qml/hifi/tablet/TabletAudioPreferences.qml
new file mode 100644
index 0000000000..efb7b5d50d
--- /dev/null
+++ b/interface/resources/qml/hifi/tablet/TabletAudioPreferences.qml
@@ -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"]
+ }
+}
diff --git a/interface/resources/qml/hifi/tablet/TabletAvatarPreferences.qml b/interface/resources/qml/hifi/tablet/TabletAvatarPreferences.qml
new file mode 100644
index 0000000000..f5c1ddf8f7
--- /dev/null
+++ b/interface/resources/qml/hifi/tablet/TabletAvatarPreferences.qml
@@ -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"]
+ }
+}
diff --git a/interface/resources/qml/hifi/tablet/TabletGeneralSettings.qml b/interface/resources/qml/hifi/tablet/TabletGeneralPreferences.qml
similarity index 83%
rename from interface/resources/qml/hifi/tablet/TabletGeneralSettings.qml
rename to interface/resources/qml/hifi/tablet/TabletGeneralPreferences.qml
index b445e6a463..4473a997e6 100644
--- a/interface/resources/qml/hifi/tablet/TabletGeneralSettings.qml
+++ b/interface/resources/qml/hifi/tablet/TabletGeneralPreferences.qml
@@ -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"]
-
}
-
}
diff --git a/interface/resources/qml/hifi/tablet/TabletMenu.qml b/interface/resources/qml/hifi/tablet/TabletMenu.qml
index e0deab64b6..d7d5130bce 100644
--- a/interface/resources/qml/hifi/tablet/TabletMenu.qml
+++ b/interface/resources/qml/hifi/tablet/TabletMenu.qml
@@ -2,8 +2,14 @@ import QtQuick 2.5
import QtGraphicalEffects 1.0
import QtQuick.Controls 1.4
import QtQml 2.2
+import QtWebChannel 1.0
+import QtWebEngine 1.1
+import HFWebEngineProfile 1.0
+
+
import "."
import "../../styles-uit"
+import "../../controls"
FocusScope {
id: tabletMenu
@@ -13,10 +19,11 @@ FocusScope {
height: 720
property var rootMenu: Menu { objectName:"rootMenu" }
- property var point: Qt.point(50, 50)
+ property var point: Qt.point(50, 50);
+ TabletMenuStack { id: menuPopperUpper }
property string subMenu: ""
-
- TabletMouseHandler { id: menuPopperUpper }
+ property var eventBridge;
+ signal sendToScript(var message);
Rectangle {
id: bgNavBar
@@ -57,6 +64,7 @@ FocusScope {
// navigate back to root level menu
onClicked: {
buildMenu();
+ breadcrumbText.text = "Menu";
tabletRoot.playButtonClickSound();
}
}
@@ -97,6 +105,7 @@ FocusScope {
menuPopperUpper.closeLastMenu();
}
+
function setRootMenu(rootMenu, subMenu) {
tabletMenu.subMenu = subMenu;
tabletMenu.rootMenu = rootMenu;
@@ -116,12 +125,12 @@ FocusScope {
}
subMenu = ""; // Continue with full menu after initially displaying submenu.
if (found) {
- menuPopperUpper.popup(tabletMenu, rootMenu.items[index].items);
+ menuPopperUpper.popup(rootMenu.items[index].items);
return;
}
}
// Otherwise build whole menu.
- menuPopperUpper.popup(tabletMenu, rootMenu.items);
+ menuPopperUpper.popup(rootMenu.items);
}
}
diff --git a/interface/resources/qml/hifi/tablet/TabletMouseHandler.qml b/interface/resources/qml/hifi/tablet/TabletMenuStack.qml
similarity index 82%
rename from interface/resources/qml/hifi/tablet/TabletMouseHandler.qml
rename to interface/resources/qml/hifi/tablet/TabletMenuStack.qml
index 17a00eccde..a75a9fcd86 100644
--- a/interface/resources/qml/hifi/tablet/TabletMouseHandler.qml
+++ b/interface/resources/qml/hifi/tablet/TabletMenuStack.qml
@@ -1,7 +1,7 @@
//
// MessageDialog.qml
//
-// Created by Bradley Austin Davis on 18 Jan 2016
+// Created by Dante Ruiz on 13 Feb 2017
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
@@ -17,19 +17,13 @@ Item {
id: root
anchors.fill: parent
objectName: "tabletMenuHandlerItem"
-
- MouseArea {
- id: menuRoot;
- objectName: "tabletMenuHandlerMouseArea"
+
+ StackView {
anchors.fill: parent
- enabled: d.topMenu !== null
- onClicked: {
- d.clearMenus();
- }
- }
-
- QtObject {
id: d
+ objectName: "stack"
+ initialItem: topMenu
+
property var menuStack: []
property var topMenu: null;
property var modelMaker: Component { ListModel { } }
@@ -53,6 +47,18 @@ Item {
}
}
+ function pushSource(path) {
+ d.push(Qt.resolvedUrl(path));
+ d.currentItem.eventBridge = tabletMenu.eventBridge
+ d.currentItem.sendToScript.connect(tabletMenu.sendToScript);
+ breadcrumbText.text = d.currentItem.objectName;
+ }
+
+ function popSource() {
+ console.log("trying to pop page");
+ d.pop();
+ }
+
function toModel(items) {
var result = modelMaker.createObject(tabletMenu);
for (var i = 0; i < items.length; ++i) {
@@ -76,22 +82,18 @@ Item {
}
function popMenu() {
- if (menuStack.length) {
- menuStack.pop().destroy();
+ if (d.depth) {
+ d.pop();
}
- if (menuStack.length) {
- topMenu = menuStack[menuStack.length - 1];
+ if (d.depth) {
+ topMenu = d.currentItem;
topMenu.focus = true;
topMenu.forceActiveFocus();
// show current menu level on nav bar
- if (topMenu.objectName === "") {
+ if (topMenu.objectName === "" || d.depth === 1) {
breadcrumbText.text = "Menu";
} else {
- if (menuStack.length === 1) {
- breadcrumbText.text = "Menu";
- } else {
- breadcrumbText.text = topMenu.objectName;
- }
+ breadcrumbText.text = topMenu.objectName;
}
} else {
breadcrumbText.text = "Menu";
@@ -100,16 +102,14 @@ Item {
}
function pushMenu(newMenu) {
- menuStack.push(newMenu);
+ d.push({ item:newMenu, destroyOnPop: true});
topMenu = newMenu;
topMenu.focus = true;
topMenu.forceActiveFocus();
}
function clearMenus() {
- while (menuStack.length) {
- popMenu()
- }
+ d.clear()
}
function clampMenuPosition(menu) {
@@ -127,7 +127,7 @@ Item {
}
}
- function buildMenu(items, targetPosition) {
+ function buildMenu(items) {
var model = toModel(items);
// Menus must be childed to desktop for Z-ordering
var newMenu = menuViewMaker.createObject(tabletMenu, { model: model, isSubMenu: topMenu !== null });
@@ -158,13 +158,13 @@ Item {
}
- function popup(parent, items) {
+ function popup(items) {
d.clearMenus();
- d.buildMenu(items, point);
+ d.buildMenu(items);
}
function closeLastMenu() {
- if (d.menuStack.length > 1) {
+ if (d.depth > 1) {
d.popMenu();
return true;
}
diff --git a/interface/resources/qml/hifi/tablet/TabletNetworkingPreferences.qml b/interface/resources/qml/hifi/tablet/TabletNetworkingPreferences.qml
new file mode 100644
index 0000000000..9b12d3c69e
--- /dev/null
+++ b/interface/resources/qml/hifi/tablet/TabletNetworkingPreferences.qml
@@ -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"]
+ }
+}
diff --git a/interface/resources/qml/hifi/tablet/TabletRoot.qml b/interface/resources/qml/hifi/tablet/TabletRoot.qml
index 1fb31e5619..99c9351993 100644
--- a/interface/resources/qml/hifi/tablet/TabletRoot.qml
+++ b/interface/resources/qml/hifi/tablet/TabletRoot.qml
@@ -1,5 +1,7 @@
import QtQuick 2.0
import Hifi 1.0
+import QtQuick.Controls 1.4
+import "../../dialogs"
Item {
id: tabletRoot
@@ -8,19 +10,50 @@ Item {
property var eventBridge;
property var rootMenu;
+ property var openModal: null;
+ property var openMessage: null;
property string subMenu: ""
-
signal showDesktop();
function setOption(value) {
option = value;
}
+ Component { id: inputDialogBuilder; TabletQueryDialog { } }
+ function inputDialog(properties) {
+ openModal = inputDialogBuilder.createObject(tabletRoot, properties);
+ return openModal;
+ }
+ Component { id: messageBoxBuilder; TabletMessageBox { } }
+ function messageBox(properties) {
+ openMessage = messageBoxBuilder.createObject(tabletRoot, properties);
+ return openMessage;
+ }
+
+ Component { id: customInputDialogBuilder; TabletCustomQueryDialog { } }
+ function customInputDialog(properties) {
+ return customInputDialogBuilder.createObject(tabletRoot, properties);
+ }
+
+ Component { id: fileDialogBuilder; TabletFileDialog { } }
+ function fileDialog(properties) {
+ openModal = fileDialogBuilder.createObject(tabletRoot, properties);
+ return openModal;
+ }
+
function setMenuProperties(rootMenu, subMenu) {
tabletRoot.rootMenu = rootMenu;
tabletRoot.subMenu = subMenu;
}
+ function isDialogOpen() {
+ if (openMessage !== null || openModal !== null) {
+ return true;
+ }
+
+ return false;
+ }
+
function loadSource(url) {
loader.source = ""; // make sure we load the qml fresh each time.
loader.source = url;
@@ -68,6 +101,7 @@ Item {
objectName: "loader"
asynchronous: false
+
width: parent.width
height: parent.height
@@ -89,6 +123,12 @@ Item {
loader.item.setRootMenu(tabletRoot.rootMenu, tabletRoot.subMenu);
}
loader.item.forceActiveFocus();
+
+ if (openModal) {
+ openModal.canceled();
+ openModal.destroy();
+ openModal = null;
+ }
}
}
diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml b/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml
index 7d214237a3..95ce7f5a1d 100644
--- a/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml
+++ b/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml
@@ -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;
}
}
}
-
}
diff --git a/interface/resources/qml/styles-uit/FiraSansSemiBold.qml b/interface/resources/qml/styles-uit/FiraSansSemiBold.qml
index ddbeff7d90..b3f3324090 100644
--- a/interface/resources/qml/styles-uit/FiraSansSemiBold.qml
+++ b/interface/resources/qml/styles-uit/FiraSansSemiBold.qml
@@ -14,7 +14,7 @@ import QtQuick.Controls.Styles 1.4
Text {
id: root
- FontLoader { id: firaSansSemiBold; source: "../../fonts/FiraSans-SemiBold.ttf"; }
+ FontLoader { id: firaSansSemiBold; source: pathToFonts + "fonts/FiraSans-SemiBold.ttf"; }
property real size: 32
font.pixelSize: size
verticalAlignment: Text.AlignVCenter
diff --git a/interface/resources/qml/styles-uit/HiFiGlyphs.qml b/interface/resources/qml/styles-uit/HiFiGlyphs.qml
index d0dae746be..cbd6fa1d68 100644
--- a/interface/resources/qml/styles-uit/HiFiGlyphs.qml
+++ b/interface/resources/qml/styles-uit/HiFiGlyphs.qml
@@ -14,7 +14,7 @@ import QtQuick.Controls.Styles 1.4
Text {
id: root
- FontLoader { id: hiFiGlyphs; source: "../../fonts/hifi-glyphs.ttf"; }
+ FontLoader { id: hiFiGlyphs; source: pathToFonts + "fonts/hifi-glyphs.ttf"; }
property int size: 32
font.pixelSize: size
width: size
diff --git a/interface/resources/qml/styles-uit/HifiConstants.qml b/interface/resources/qml/styles-uit/HifiConstants.qml
index 031e80283e..38534d4243 100644
--- a/interface/resources/qml/styles-uit/HifiConstants.qml
+++ b/interface/resources/qml/styles-uit/HifiConstants.qml
@@ -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 {
diff --git a/interface/resources/qml/styles-uit/RalewayBold.qml b/interface/resources/qml/styles-uit/RalewayBold.qml
index 97a6a4c208..433fdb7ae6 100644
--- a/interface/resources/qml/styles-uit/RalewayBold.qml
+++ b/interface/resources/qml/styles-uit/RalewayBold.qml
@@ -14,7 +14,7 @@ import QtQuick.Controls.Styles 1.4
Text {
id: root
- FontLoader { id: ralewayBold; source: "../../fonts/Raleway-Bold.ttf"; }
+ FontLoader { id: ralewayBold; source: pathToFonts + "fonts/Raleway-Bold.ttf"; }
property real size: 32
font.pixelSize: size
verticalAlignment: Text.AlignVCenter
diff --git a/interface/resources/qml/styles-uit/RalewayRegular.qml b/interface/resources/qml/styles-uit/RalewayRegular.qml
index 1ed5f122dc..2cffeeb59d 100644
--- a/interface/resources/qml/styles-uit/RalewayRegular.qml
+++ b/interface/resources/qml/styles-uit/RalewayRegular.qml
@@ -14,7 +14,7 @@ import QtQuick.Controls.Styles 1.4
Text {
id: root
- FontLoader { id: ralewayRegular; source: "../../fonts/Raleway-Regular.ttf"; }
+ FontLoader { id: ralewayRegular; source: pathToFonts + "fonts/Raleway-Regular.ttf"; }
property real size: 32
font.pixelSize: size
verticalAlignment: Text.AlignVCenter
diff --git a/interface/resources/qml/styles-uit/RalewaySemiBold.qml b/interface/resources/qml/styles-uit/RalewaySemiBold.qml
index 3c36a872a4..b6c79e02a4 100644
--- a/interface/resources/qml/styles-uit/RalewaySemiBold.qml
+++ b/interface/resources/qml/styles-uit/RalewaySemiBold.qml
@@ -14,7 +14,7 @@ import QtQuick.Controls.Styles 1.4
Text {
id: root
- FontLoader { id: ralewaySemiBold; source: "../../fonts/Raleway-SemiBold.ttf"; }
+ FontLoader { id: ralewaySemiBold; source: pathToFonts + "fonts/Raleway-SemiBold.ttf"; }
property real size: 32
font.pixelSize: size
verticalAlignment: Text.AlignVCenter
diff --git a/interface/resources/qml/windows/TabletModalFrame.qml b/interface/resources/qml/windows/TabletModalFrame.qml
new file mode 100644
index 0000000000..550eec8357
--- /dev/null
+++ b/interface/resources/qml/windows/TabletModalFrame.qml
@@ -0,0 +1,89 @@
+//
+// ModalFrame.qml
+//
+// Created by Bradley Austin Davis on 15 Jan 2016
+// Copyright 2015 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+import QtQuick 2.5
+
+import "."
+import "../controls-uit"
+import "../styles-uit"
+
+
+Rectangle {
+ HifiConstants { id: hifi }
+
+ id: frameContent
+
+ readonly property bool hasTitle: root.title != ""
+
+ readonly property int frameMarginLeft: hifi.dimensions.modalDialogMargin.x
+ readonly property int frameMarginRight: hifi.dimensions.modalDialogMargin.x
+ readonly property int frameMarginTop: hifi.dimensions.modalDialogMargin.y + (frameContent.hasTitle ? hifi.dimensions.modalDialogTitleHeight + 10 : 0)
+ readonly property int frameMarginBottom: hifi.dimensions.modalDialogMargin.y
+
+ border {
+ width: hifi.dimensions.borderWidth
+ color: hifi.colors.lightGrayText80
+ }
+
+ radius: hifi.dimensions.borderRadius
+ color: hifi.colors.faintGray
+ Item {
+ id: frameTitle
+ visible: frameContent.hasTitle
+
+ anchors {
+ horizontalCenter: parent.horizontalCenter
+ verticalCenter: parent.verticalCenter
+ fill: parent
+ topMargin: frameMarginTop
+ leftMargin: frameMarginLeft
+ rightMargin: frameMarginRight
+ //bottomMargin: frameMarginBottom
+ }
+
+ Item {
+ width: title.width + (icon.text !== "" ? icon.width + hifi.dimensions.contentSpacing.x : 20)
+
+ onWidthChanged: root.titleWidth = width
+
+ HiFiGlyphs {
+ id: icon
+ text: root.iconText ? root.iconText : ""
+ size: root.iconSize ? root.iconSize : 30
+ color: hifi.colors.lightGray
+ visible: true
+ anchors.verticalCenter: title.verticalCenter
+ anchors.leftMargin: 50
+ anchors.left: parent.left
+ }
+
+ RalewayRegular {
+ id: title
+ text: root.title
+ elide: Text.ElideRight
+ color: hifi.colors.baseGrayHighlight
+ size: hifi.fontSizes.overlayTitle
+ y: -hifi.dimensions.modalDialogTitleHeight
+ anchors.rightMargin: -50
+ anchors.right: parent.right
+ //anchors.horizontalCenter: parent.horizontalCenter
+ }
+ }
+
+ Rectangle {
+ anchors.left: parent.left
+ anchors.right: parent.right
+ height: 1
+ color: hifi.colors.lightGray
+ }
+
+ }
+
+}
diff --git a/interface/resources/qml/windows/TabletModalWindow.qml b/interface/resources/qml/windows/TabletModalWindow.qml
new file mode 100644
index 0000000000..05f192f7a7
--- /dev/null
+++ b/interface/resources/qml/windows/TabletModalWindow.qml
@@ -0,0 +1,22 @@
+//
+// ModalWindow.qml
+//
+// Created by Bradley Austin Davis on 22 Jan 2016
+// Copyright 2015 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+import QtQuick 2.5
+import QtQuick.Controls 1.4
+import QtQuick.Dialogs 1.2 as OriginalDialogs
+import "."
+
+Rectangle {
+ id: modalWindow
+ layer.enabled: true
+ property var title: "Modal"
+ width: tabletRoot.width
+ height: tabletRoot.height
+ color: "#80000000"
+}
diff --git a/interface/resources/qml/windows/Window.qml b/interface/resources/qml/windows/Window.qml
index 20216ed7ae..a0ef73290a 100644
--- a/interface/resources/qml/windows/Window.qml
+++ b/interface/resources/qml/windows/Window.qml
@@ -313,6 +313,6 @@ Fadable {
}
}
- onMouseEntered: console.log("Mouse entered " + window)
- onMouseExited: console.log("Mouse exited " + window)
+ // onMouseEntered: console.log("Mouse entered " + window)
+ // onMouseExited: console.log("Mouse exited " + window)
}
diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index e35dcc674d..27dd65ab8d 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -499,6 +499,7 @@ bool setupEssentials(int& argc, char** argv) {
DependencyManager::set();
DependencyManager::set();
DependencyManager::set();
+ DependencyManager::set();
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
DependencyManager::set();
@@ -847,6 +848,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
connect(this, &QCoreApplication::aboutToQuit, addressManager.data(), &AddressManager::storeCurrentAddress);
connect(this, &Application::activeDisplayPluginChanged, this, &Application::updateThreadPoolCount);
+ connect(this, &Application::activeDisplayPluginChanged, this, [](){
+ qApp->setProperty(hifi::properties::HMD, qApp->isHMDMode());
+ });
connect(this, &Application::activeDisplayPluginChanged, this, &Application::updateSystemTabletMode);
// Save avatar location immediately after a teleport.
@@ -1619,17 +1623,14 @@ 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;
+ auto tabletScriptingInterface = DependencyManager::get();
+ TabletProxy* tablet = dynamic_cast(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
+ bool messageOpen = tablet->isMessageDialogOpen();
+ if (!messageOpen) {
+ auto HMD = DependencyManager::get();
+ HMD->toggleShouldShowTablet();
}
- lastTabletUIToggle = now;
-
- auto HMD = DependencyManager::get();
- HMD->toggleShouldShowTablet();
}
void Application::checkChangeCursor() {
@@ -1961,12 +1962,13 @@ void Application::initializeUi() {
rootContext->setContextProperty("AddressManager", DependencyManager::get().data());
rootContext->setContextProperty("FrameTimings", &_frameTimingsScriptingInterface);
rootContext->setContextProperty("Rates", new RatesScriptingInterface(this));
+ rootContext->setContextProperty("pathToFonts", "../../");
rootContext->setContextProperty("TREE_SCALE", TREE_SCALE);
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().data());
rootContext->setContextProperty("AvatarList", DependencyManager::get().data());
rootContext->setContextProperty("Users", DependencyManager::get().data());
@@ -5790,8 +5792,23 @@ bool Application::displayAvatarAttachmentConfirmationDialog(const QString& name)
}
void Application::toggleRunningScriptsWidget() const {
- static const QUrl url("hifi/dialogs/RunningScripts.qml");
- DependencyManager::get()->show(url, "RunningScripts");
+
+ auto tabletScriptingInterface = DependencyManager::get();
+ auto tablet = dynamic_cast(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
+ if (tablet->getToolbarMode()) {
+ static const QUrl url("hifi/dialogs/RunningScripts.qml");
+ DependencyManager::get()->show(url, "RunningScripts");
+ } else {
+ QQuickItem* tabletRoot = tablet->getTabletRoot();
+ if (!tabletRoot && !isHMDMode()) {
+ static const QUrl url("hifi/dialogs/RunningScripts.qml");
+ DependencyManager::get()->show(url, "RunningScripts");
+ } else {
+ static const QUrl url("../../hifi/dialogs/TabletRunningScripts.qml");
+ tablet->pushOntoStack(url);
+ }
+ }
+ //DependencyManager::get()->show(url, "RunningScripts");
//if (_runningScriptsWidget->isVisible()) {
// if (_runningScriptsWidget->hasFocus()) {
// _runningScriptsWidget->hide();
@@ -5818,7 +5835,21 @@ void Application::showAssetServerWidget(QString filePath) {
emit uploadRequest(filePath);
}
};
- DependencyManager::get()->show(url, "AssetServer", startUpload);
+ auto tabletScriptingInterface = DependencyManager::get();
+ auto tablet = dynamic_cast(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
+
+ if (tablet->getToolbarMode()) {
+ DependencyManager::get()->show(url, "AssetServer", startUpload);
+ } else {
+ QQuickItem* tabletRoot = tablet->getTabletRoot();
+ if (!tabletRoot && !isHMDMode()) {
+ DependencyManager::get()->show(url, "AssetServer", startUpload);
+ } else {
+ static const QUrl url("../../hifi/dialogs/TabletAssetServer.qml");
+ tablet->pushOntoStack(url);
+ }
+ }
+
startUpload(nullptr, nullptr);
}
@@ -5841,6 +5872,16 @@ void Application::addAssetToWorldFromURL(QString url) {
request->send();
}
+void Application::showDialog(const QString& desktopURL, const QString& tabletURL, const QString& name) const {
+ auto tabletScriptingInterface = DependencyManager::get();
+ auto tablet = dynamic_cast(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
+ if (tablet->getToolbarMode() || (!tablet->getTabletRoot() && !isHMDMode())) {
+ DependencyManager::get()->show(desktopURL, name);
+ } else {
+ tablet->loadQMLSource(tabletURL);
+ }
+}
+
void Application::addAssetToWorldFromURLRequestFinished() {
auto request = qobject_cast(sender());
auto url = request->getUrl().toString();
@@ -6908,6 +6949,7 @@ void Application::updateThreadPoolCount() const {
}
void Application::updateSystemTabletMode() {
+ qApp->setProperty(hifi::properties::HMD, isHMDMode());
if (isHMDMode()) {
DependencyManager::get()->setToolbarMode(getHmdTabletBecomesToolbarSetting());
} else {
diff --git a/interface/src/Application.h b/interface/src/Application.h
index c4ba760153..9fab4aef81 100644
--- a/interface/src/Application.h
+++ b/interface/src/Application.h
@@ -333,6 +333,8 @@ public slots:
void toggleRunningScriptsWidget() const;
Q_INVOKABLE void showAssetServerWidget(QString filePath = "");
+ void showDialog(const QString& desktopURL, const QString& tabletURL, const QString& name) const;
+
// FIXME: Move addAssetToWorld* methods to own class?
void addAssetToWorldFromURL(QString url);
void addAssetToWorldFromURLRequestFinished();
diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp
index beacbaccab..241f908190 100644
--- a/interface/src/Menu.cpp
+++ b/interface/src/Menu.cpp
@@ -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()->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()->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()->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()->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,
diff --git a/interface/src/scripting/AssetMappingsScriptingInterface.h b/interface/src/scripting/AssetMappingsScriptingInterface.h
index 459f01b512..b7fcea2491 100644
--- a/interface/src/scripting/AssetMappingsScriptingInterface.h
+++ b/interface/src/scripting/AssetMappingsScriptingInterface.h
@@ -20,6 +20,8 @@
#include
#include
+#include "DependencyManager.h"
+
class AssetMappingModel : public QStandardItemModel {
Q_OBJECT
@@ -39,10 +41,12 @@ private:
QHash _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:
diff --git a/interface/src/scripting/HMDScriptingInterface.cpp b/interface/src/scripting/HMDScriptingInterface.cpp
index 2bca793d80..e2fed40a6d 100644
--- a/interface/src/scripting/HMDScriptingInterface.cpp
+++ b/interface/src/scripting/HMDScriptingInterface.cpp
@@ -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();
@@ -131,7 +135,7 @@ glm::quat HMDScriptingInterface::getOrientation() const {
return glm::quat();
}
-bool HMDScriptingInterface::isMounted() const{
+bool HMDScriptingInterface::isMounted() const {
auto displayPlugin = qApp->getActiveDisplayPlugin();
return (displayPlugin->isHmd() && displayPlugin->isDisplayVisible());
}
diff --git a/interface/src/scripting/HMDScriptingInterface.h b/interface/src/scripting/HMDScriptingInterface.h
index d895d5da4c..276e23d2d5 100644
--- a/interface/src/scripting/HMDScriptingInterface.h
+++ b/interface/src/scripting/HMDScriptingInterface.h
@@ -76,6 +76,8 @@ public:
Q_INVOKABLE void closeTablet();
+ Q_INVOKABLE void openTablet();
+
signals:
bool shouldShowHandControllersChanged();
diff --git a/interface/src/ui/overlays/Overlay.cpp b/interface/src/ui/overlays/Overlay.cpp
index 0ad2c94241..764422019e 100644
--- a/interface/src/ui/overlays/Overlay.cpp
+++ b/interface/src/ui/overlays/Overlay.cpp
@@ -100,6 +100,9 @@ void Overlay::setProperties(const QVariantMap& properties) {
}
QVariant Overlay::getProperty(const QString& property) {
+ if (property == "type") {
+ return QVariant(getType());
+ }
if (property == "color") {
return xColorToVariant(_color);
}
diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp
index ad7fbd6cc2..9847961f5a 100644
--- a/interface/src/ui/overlays/Overlays.cpp
+++ b/interface/src/ui/overlays/Overlays.cpp
@@ -711,10 +711,9 @@ PointerEvent Overlays::calculatePointerEvent(Overlay::Pointer overlay, PickRay r
auto dimensions = thisOverlay->getSize();
glm::vec2 pos2D = projectOntoOverlayXYPlane(position, rotation, dimensions, ray, rayPickResult);
- PointerEvent pointerEvent(eventType, MOUSE_POINTER_ID,
- pos2D, rayPickResult.intersection,
- rayPickResult.surfaceNormal, ray.direction,
- toPointerButton(*event), toPointerButtons(*event));
+
+ PointerEvent pointerEvent(eventType, MOUSE_POINTER_ID, pos2D, rayPickResult.intersection, rayPickResult.surfaceNormal,
+ ray.direction, toPointerButton(*event), toPointerButtons(*event), event->modifiers());
return pointerEvent;
}
diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp
index 7b9e075d64..32fe26a697 100644
--- a/interface/src/ui/overlays/Web3DOverlay.cpp
+++ b/interface/src/ui/overlays/Web3DOverlay.cpp
@@ -37,8 +37,12 @@
#include
#include "scripting/AccountScriptingInterface.h"
#include "scripting/HMDScriptingInterface.h"
+#include "scripting/AssetMappingsScriptingInterface.h"
#include
+#include
#include "FileDialogHelper.h"
+#include "avatar/AvatarManager.h"
+#include "AudioClient.h"
static const float DPI = 30.47f;
static const float INCHES_TO_METERS = 1.0f / 39.3701f;
@@ -161,6 +165,10 @@ void Web3DOverlay::loadSourceURL() {
_webSurface->getRootContext()->setContextProperty("HMD", DependencyManager::get().data());
_webSurface->getRootContext()->setContextProperty("UserActivityLogger", DependencyManager::get().data());
_webSurface->getRootContext()->setContextProperty("Preferences", DependencyManager::get().data());
+ _webSurface->getRootContext()->setContextProperty("Vec3", new Vec3());
+ _webSurface->getRootContext()->setContextProperty("Quat", new Quat());
+ _webSurface->getRootContext()->setContextProperty("MyAvatar", DependencyManager::get()->getMyAvatar().get());
+ _webSurface->getRootContext()->setContextProperty("Entities", DependencyManager::get().data());
if (_webSurface->getRootItem() && _webSurface->getRootItem()->objectName() == "tabletRoot") {
auto tabletScriptingInterface = DependencyManager::get();
@@ -168,17 +176,30 @@ void Web3DOverlay::loadSourceURL() {
_webSurface->getRootContext()->setContextProperty("offscreenFlags", flags);
_webSurface->getRootContext()->setContextProperty("AddressManager", DependencyManager::get().data());
_webSurface->getRootContext()->setContextProperty("Account", AccountScriptingInterface::getInstance());
+ _webSurface->getRootContext()->setContextProperty("AudioStats", DependencyManager::get()->getStats().data());
_webSurface->getRootContext()->setContextProperty("HMD", DependencyManager::get().data());
_webSurface->getRootContext()->setContextProperty("fileDialogHelper", new FileDialogHelper());
+ _webSurface->getRootContext()->setContextProperty("ScriptDiscoveryService", DependencyManager::get().data());
+ _webSurface->getRootContext()->setContextProperty("Tablet", DependencyManager::get().data());
+ _webSurface->getRootContext()->setContextProperty("Assets", DependencyManager::get().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;
@@ -188,9 +209,11 @@ void Web3DOverlay::render(RenderArgs* args) {
QSurface * currentSurface = currentContext->surface();
if (!_webSurface) {
_webSurface = DependencyManager::get()->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));
@@ -240,6 +263,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;
@@ -353,9 +380,8 @@ void Web3DOverlay::handlePointerEvent(const PointerEvent& event) {
QList touchPoints;
touchPoints.push_back(point);
- QTouchEvent* touchEvent = new QTouchEvent(type);
+ QTouchEvent* touchEvent = new QTouchEvent(type, &_touchDevice, event.getKeyboardModifiers());
touchEvent->setWindow(_webSurface->getWindow());
- touchEvent->setDevice(&_touchDevice);
touchEvent->setTarget(_webSurface->getRootItem());
touchEvent->setTouchPoints(touchPoints);
touchEvent->setTouchPointStates(touchPointState);
@@ -396,6 +422,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();
@@ -415,6 +446,9 @@ QVariant Web3DOverlay::getProperty(const QString& property) {
if (property == "dpi") {
return _dpi;
}
+ if (property == "maxFPS") {
+ return _desiredMaxFPS;
+ }
if (property == "showKeyboardFocusHighlight") {
return _showKeyboardFocusHighlight;
}
diff --git a/interface/src/ui/overlays/Web3DOverlay.h b/interface/src/ui/overlays/Web3DOverlay.h
index 2b9686919d..e71cac2452 100644
--- a/interface/src/ui/overlays/Web3DOverlay.h
+++ b/interface/src/ui/overlays/Web3DOverlay.h
@@ -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;
diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp
index bd25bcf905..7e2d78a837 100644
--- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp
+++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp
@@ -713,7 +713,8 @@ void EntityTreeRenderer::mousePressEvent(QMouseEvent* event) {
PointerEvent pointerEvent(PointerEvent::Press, MOUSE_POINTER_ID,
pos2D, rayPickResult.intersection,
rayPickResult.surfaceNormal, ray.direction,
- toPointerButton(*event), toPointerButtons(*event));
+ toPointerButton(*event), toPointerButtons(*event),
+ Qt::NoModifier); // TODO -- check for modifier keys?
emit mousePressOnEntity(rayPickResult.entityID, pointerEvent);
@@ -753,7 +754,8 @@ void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event) {
PointerEvent pointerEvent(PointerEvent::Release, MOUSE_POINTER_ID,
pos2D, rayPickResult.intersection,
rayPickResult.surfaceNormal, ray.direction,
- toPointerButton(*event), toPointerButtons(*event));
+ toPointerButton(*event), toPointerButtons(*event),
+ Qt::NoModifier); // TODO -- check for modifier keys?
emit mouseReleaseOnEntity(rayPickResult.entityID, pointerEvent);
if (_entitiesScriptEngine) {
@@ -773,7 +775,8 @@ void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event) {
PointerEvent pointerEvent(PointerEvent::Release, MOUSE_POINTER_ID,
pos2D, rayPickResult.intersection,
rayPickResult.surfaceNormal, ray.direction,
- toPointerButton(*event), toPointerButtons(*event));
+ toPointerButton(*event), toPointerButtons(*event),
+ Qt::NoModifier); // TODO -- check for modifier keys?
emit clickReleaseOnEntity(_currentClickingOnEntityID, pointerEvent);
if (_entitiesScriptEngine) {
@@ -803,7 +806,8 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) {
PointerEvent pointerEvent(PointerEvent::Move, MOUSE_POINTER_ID,
pos2D, rayPickResult.intersection,
rayPickResult.surfaceNormal, ray.direction,
- toPointerButton(*event), toPointerButtons(*event));
+ toPointerButton(*event), toPointerButtons(*event),
+ Qt::NoModifier); // TODO -- check for modifier keys?
emit mouseMoveOnEntity(rayPickResult.entityID, pointerEvent);
@@ -823,7 +827,8 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) {
PointerEvent pointerEvent(PointerEvent::Move, MOUSE_POINTER_ID,
pos2D, rayPickResult.intersection,
rayPickResult.surfaceNormal, ray.direction,
- toPointerButton(*event), toPointerButtons(*event));
+ toPointerButton(*event), toPointerButtons(*event),
+ Qt::NoModifier); // TODO -- check for modifier keys?
emit hoverLeaveEntity(_currentHoverOverEntityID, pointerEvent);
if (_entitiesScriptEngine) {
@@ -864,7 +869,8 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) {
PointerEvent pointerEvent(PointerEvent::Move, MOUSE_POINTER_ID,
pos2D, rayPickResult.intersection,
rayPickResult.surfaceNormal, ray.direction,
- toPointerButton(*event), toPointerButtons(*event));
+ toPointerButton(*event), toPointerButtons(*event),
+ Qt::NoModifier); // TODO -- check for modifier keys?
emit hoverLeaveEntity(_currentHoverOverEntityID, pointerEvent);
if (_entitiesScriptEngine) {
@@ -883,7 +889,8 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) {
PointerEvent pointerEvent(PointerEvent::Move, MOUSE_POINTER_ID,
pos2D, rayPickResult.intersection,
rayPickResult.surfaceNormal, ray.direction,
- toPointerButton(*event), toPointerButtons(*event));
+ toPointerButton(*event), toPointerButtons(*event),
+ Qt::NoModifier); // TODO -- check for modifier keys?
emit holdingClickOnEntity(_currentClickingOnEntityID, pointerEvent);
if (_entitiesScriptEngine) {
diff --git a/libraries/gl/src/gl/OffscreenQmlSurface.cpp b/libraries/gl/src/gl/OffscreenQmlSurface.cpp
index 447b9d56aa..de0caf56a9 100644
--- a/libraries/gl/src/gl/OffscreenQmlSurface.cpp
+++ b/libraries/gl/src/gl/OffscreenQmlSurface.cpp
@@ -32,6 +32,7 @@
#include
#include
#include
+#include
#include "OffscreenGLCanvas.h"
#include "GLHelpers.h"
@@ -433,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) {
@@ -911,20 +913,23 @@ void OffscreenQmlSurface::setKeyboardRaised(QObject* object, bool raised, bool n
return;
}
- QQuickItem* item = dynamic_cast(object);
- while (item) {
- // Numeric value may be set in parameter from HTML UI; for QML UI, detect numeric fields here.
- numeric = numeric || QString(item->metaObject()->className()).left(7) == "SpinBox";
+ // if HMD is being worn, allow keyboard to open. allow it to close, HMD or not.
+ if (!raised || qApp->property(hifi::properties::HMD).toBool()) {
+ QQuickItem* item = dynamic_cast(object);
+ while (item) {
+ // Numeric value may be set in parameter from HTML UI; for QML UI, detect numeric fields here.
+ numeric = numeric || QString(item->metaObject()->className()).left(7) == "SpinBox";
- if (item->property("keyboardRaised").isValid()) {
- // FIXME - HMD only: Possibly set value of "keyboardEnabled" per isHMDMode() for use in WebView.qml.
- if (item->property("punctuationMode").isValid()) {
- item->setProperty("punctuationMode", QVariant(numeric));
+ if (item->property("keyboardRaised").isValid()) {
+ // FIXME - HMD only: Possibly set value of "keyboardEnabled" per isHMDMode() for use in WebView.qml.
+ if (item->property("punctuationMode").isValid()) {
+ item->setProperty("punctuationMode", QVariant(numeric));
+ }
+ item->setProperty("keyboardRaised", QVariant(raised));
+ return;
}
- item->setProperty("keyboardRaised", QVariant(raised));
- return;
+ item = dynamic_cast(item->parentItem());
}
- item = dynamic_cast(item->parentItem());
}
}
diff --git a/libraries/script-engine/src/TabletScriptingInterface.cpp b/libraries/script-engine/src/TabletScriptingInterface.cpp
index 32bd7f422e..e26b3a3f63 100644
--- a/libraries/script-engine/src/TabletScriptingInterface.cpp
+++ b/libraries/script-engine/src/TabletScriptingInterface.cpp
@@ -250,6 +250,18 @@ static QString getUsername() {
}
}
+bool TabletProxy::isMessageDialogOpen() {
+ if (_qmlTabletRoot) {
+ QVariant result;
+ QMetaObject::invokeMethod(_qmlTabletRoot, "isDialogOpen",Qt::DirectConnection,
+ Q_RETURN_ARG(QVariant, result));
+
+ return result.toBool();
+ }
+
+ return false;
+}
+
void TabletProxy::setQmlTabletRoot(QQuickItem* qmlTabletRoot, QObject* qmlOffscreenSurface) {
std::lock_guard guard(_mutex);
_qmlOffscreenSurface = qmlOffscreenSurface;
@@ -275,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())));
@@ -293,6 +306,9 @@ void TabletProxy::setQmlTabletRoot(QQuickItem* qmlTabletRoot, QObject* qmlOffscr
}
}
+void TabletProxy::gotoHomeScreen() {
+ loadHomeScreen(false);
+}
void TabletProxy::gotoMenuScreen(const QString& submenu) {
QObject* root = nullptr;
@@ -331,11 +347,53 @@ void TabletProxy::loadQMLSource(const QVariant& path) {
emit screenChanged(QVariant("QML"), path);
QMetaObject::invokeMethod(root, "setShown", Q_ARG(const QVariant&, QVariant(true)));
}
+ } else {
+ qCDebug(scriptengine) << "tablet cannot load QML because _qmlTabletRoot is null";
}
}
-void TabletProxy::gotoHomeScreen() {
- if (_state != State::Home) {
+void TabletProxy::pushOntoStack(const QVariant& path) {
+ if (_qmlTabletRoot) {
+ auto stack = _qmlTabletRoot->findChild("stack");
+ if (stack) {
+ QMetaObject::invokeMethod(stack, "pushSource", Q_ARG(const QVariant&, path));
+ } else {
+ qCDebug(scriptengine) << "tablet cannot push QML because _qmlTabletRoot doesn't have child stack";
+ }
+ } else if (_desktopWindow) {
+ auto stack = _desktopWindow->asQuickItem()->findChild("stack");
+ if (stack) {
+ QMetaObject::invokeMethod(stack, "pushSource", Q_ARG(const QVariant&, path));
+ } else {
+ qCDebug(scriptengine) << "tablet cannot push QML because _desktopWindow doesn't have child stack";
+ }
+ } else {
+ qCDebug(scriptengine) << "tablet cannot push QML because _qmlTabletRoot or _desktopWindow is null";
+ }
+}
+
+void TabletProxy::popFromStack() {
+ if (_qmlTabletRoot) {
+ auto stack = _qmlTabletRoot->findChild("stack");
+ if (stack) {
+ QMetaObject::invokeMethod(stack, "popSource");
+ } else {
+ qCDebug(scriptengine) << "tablet cannot push QML because _qmlTabletRoot doesn't have child stack";
+ }
+ } else if (_desktopWindow) {
+ auto stack = _desktopWindow->asQuickItem()->findChild("stack");
+ if (stack) {
+ QMetaObject::invokeMethod(stack, "popSource");
+ } else {
+ qCDebug(scriptengine) << "tablet cannot pop QML because _desktopWindow doesn't have child stack";
+ }
+ } else {
+ qCDebug(scriptengine) << "tablet cannot pop QML because _qmlTabletRoot or _desktopWindow is null";
+ }
+}
+
+void TabletProxy::loadHomeScreen(bool forceOntoHomeScreen) {
+ if ((_state != State::Home && _state != State::Uninitialized) || forceOntoHomeScreen) {
if (!_toolbarMode && _qmlTabletRoot) {
auto loader = _qmlTabletRoot->findChild("loader");
QObject::connect(loader, SIGNAL(loaded()), this, SLOT(addButtonsToHomeScreen()), Qt::DirectConnection);
@@ -560,7 +618,7 @@ QQuickItem* TabletProxy::getQmlTablet() const {
}
QQuickItem* TabletProxy::getQmlMenu() const {
- if (!_qmlTabletRoot) {
+ if (!_qmlTabletRoot) {
return nullptr;
}
diff --git a/libraries/script-engine/src/TabletScriptingInterface.h b/libraries/script-engine/src/TabletScriptingInterface.h
index e450923758..00624254d7 100644
--- a/libraries/script-engine/src/TabletScriptingInterface.h
+++ b/libraries/script-engine/src/TabletScriptingInterface.h
@@ -106,6 +106,14 @@ public:
Q_INVOKABLE void gotoWebScreen(const QString& url, const QString& injectedJavaScriptUrl);
Q_INVOKABLE void loadQMLSource(const QVariant& path);
+ Q_INVOKABLE void pushOntoStack(const QVariant& path);
+ Q_INVOKABLE void popFromStack();
+
+ /** jsdoc
+ * Check if the tablet has a message dialog open
+ * @function TabletProxy#isMessageDialogOpen
+ */
+ Q_INVOKABLE bool isMessageDialogOpen();
/**jsdoc
* Creates a new button, adds it to this and returns it.
@@ -150,8 +158,14 @@ public:
*/
Q_INVOKABLE void sendToQml(QVariant msg);
+ /**jsdoc
+ * Check if the tablet is on the homescreen
+ * @function TabletProxy#onHomeScreen()
+ */
Q_INVOKABLE bool onHomeScreen();
+ QQuickItem* getTabletRoot() const { return _qmlTabletRoot; }
+
QObject* getTabletSurface();
QQuickItem* getQmlTablet() const;
@@ -188,6 +202,7 @@ protected slots:
void desktopWindowClosed();
protected:
void removeButtonsFromHomeScreen();
+ void loadHomeScreen(bool forceOntoHomeScreen);
void addButtonsToToolbar();
void removeButtonsFromToolbar();
diff --git a/libraries/shared/src/PointerEvent.cpp b/libraries/shared/src/PointerEvent.cpp
index ed9acb9ada..0833657886 100644
--- a/libraries/shared/src/PointerEvent.cpp
+++ b/libraries/shared/src/PointerEvent.cpp
@@ -25,9 +25,9 @@ PointerEvent::PointerEvent() {
}
PointerEvent::PointerEvent(EventType type, uint32_t id,
- const glm::vec2& pos2D, const glm::vec3& pos3D,
- const glm::vec3& normal, const glm::vec3& direction,
- Button button, uint32_t buttons) :
+ const glm::vec2& pos2D, const glm::vec3& pos3D,
+ const glm::vec3& normal, const glm::vec3& direction,
+ Button button, uint32_t buttons, Qt::KeyboardModifiers keyboardModifiers) :
_type(type),
_id(id),
_pos2D(pos2D),
@@ -35,7 +35,8 @@ PointerEvent::PointerEvent(EventType type, uint32_t id,
_normal(normal),
_direction(direction),
_button(button),
- _buttons(buttons)
+ _buttons(buttons),
+ _keyboardModifiers(keyboardModifiers)
{
;
}
@@ -119,6 +120,8 @@ QScriptValue PointerEvent::toScriptValue(QScriptEngine* engine, const PointerEve
obj.setProperty("isSecondaryHeld", areFlagsSet(event._buttons, SecondaryButton));
obj.setProperty("isTertiaryHeld", areFlagsSet(event._buttons, TertiaryButton));
+ obj.setProperty("keyboardModifiers", QScriptValue(event.getKeyboardModifiers()));
+
return obj;
}
@@ -174,5 +177,7 @@ void PointerEvent::fromScriptValue(const QScriptValue& object, PointerEvent& eve
if (tertiary) {
event._buttons |= TertiaryButton;
}
+
+ event._keyboardModifiers = (Qt::KeyboardModifiers)(object.property("keyboardModifiers").toUInt32());
}
}
diff --git a/libraries/shared/src/PointerEvent.h b/libraries/shared/src/PointerEvent.h
index 054835c4fc..980510b091 100644
--- a/libraries/shared/src/PointerEvent.h
+++ b/libraries/shared/src/PointerEvent.h
@@ -12,6 +12,8 @@
#ifndef hifi_PointerEvent_h
#define hifi_PointerEvent_h
+#include
+
#include
#include
#include
@@ -35,7 +37,7 @@ public:
PointerEvent(EventType type, uint32_t id,
const glm::vec2& pos2D, const glm::vec3& pos3D,
const glm::vec3& normal, const glm::vec3& direction,
- Button button, uint32_t buttons);
+ Button button, uint32_t buttons, Qt::KeyboardModifiers keyboardModifiers);
static QScriptValue toScriptValue(QScriptEngine* engine, const PointerEvent& event);
static void fromScriptValue(const QScriptValue& object, PointerEvent& event);
@@ -50,6 +52,7 @@ public:
const glm::vec3& getDirection() const { return _direction; }
Button getButton() const { return _button; }
uint32_t getButtons() const { return _buttons; }
+ Qt::KeyboardModifiers getKeyboardModifiers() const { return _keyboardModifiers; }
private:
EventType _type;
@@ -61,6 +64,7 @@ private:
Button _button { NoButtons }; // button assosiated with this event, (if type is Press, this will be the button that is pressed)
uint32_t _buttons { NoButtons }; // the current state of all the buttons.
+ Qt::KeyboardModifiers _keyboardModifiers; // set of keys held when event was generated
};
Q_DECLARE_METATYPE(PointerEvent)
diff --git a/libraries/shared/src/shared/GlobalAppProperties.cpp b/libraries/shared/src/shared/GlobalAppProperties.cpp
index f2d8990708..b0ba0bf83d 100644
--- a/libraries/shared/src/shared/GlobalAppProperties.cpp
+++ b/libraries/shared/src/shared/GlobalAppProperties.cpp
@@ -16,6 +16,7 @@ namespace hifi { namespace properties {
const char* OCULUS_STORE = "com.highfidelity.oculusStore";
const char* TEST = "com.highfidelity.test";
const char* TRACING = "com.highfidelity.tracing";
+ const char* HMD = "com.highfidelity.hmd";
namespace gl {
const char* BACKEND = "com.highfidelity.gl.backend";
diff --git a/libraries/shared/src/shared/GlobalAppProperties.h b/libraries/shared/src/shared/GlobalAppProperties.h
index 609f2afd94..b1811586ba 100644
--- a/libraries/shared/src/shared/GlobalAppProperties.h
+++ b/libraries/shared/src/shared/GlobalAppProperties.h
@@ -18,6 +18,7 @@ namespace hifi { namespace properties {
extern const char* OCULUS_STORE;
extern const char* TEST;
extern const char* TRACING;
+ extern const char* HMD;
namespace gl {
extern const char* BACKEND;
diff --git a/libraries/ui/CMakeLists.txt b/libraries/ui/CMakeLists.txt
index cc2382926f..f2b48446fe 100644
--- a/libraries/ui/CMakeLists.txt
+++ b/libraries/ui/CMakeLists.txt
@@ -1,3 +1,3 @@
set(TARGET_NAME ui)
setup_hifi_library(OpenGL Network Qml Quick Script WebChannel WebSockets XmlPatterns)
-link_hifi_libraries(shared networking gl)
+link_hifi_libraries(shared networking gl script-engine)
diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp
index 7724a409f0..982d58464f 100644
--- a/libraries/ui/src/OffscreenUi.cpp
+++ b/libraries/ui/src/OffscreenUi.cpp
@@ -19,7 +19,8 @@
#include
#include
-
+#include
+#include
#include "FileDialogHelper.h"
#include "VrMenu.h"
@@ -210,9 +211,19 @@ QQuickItem* OffscreenUi::createMessageBox(Icon icon, const QString& title, const
map.insert("buttons", buttons.operator int());
map.insert("defaultButton", defaultButton);
QVariant result;
- bool invokeResult = QMetaObject::invokeMethod(_desktop, "messageBox",
- Q_RETURN_ARG(QVariant, result),
- Q_ARG(QVariant, QVariant::fromValue(map)));
+ bool invokeResult;
+ auto tabletScriptingInterface = DependencyManager::get();
+ TabletProxy* tablet = dynamic_cast(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
+ if (tablet->getToolbarMode()) {
+ invokeResult = QMetaObject::invokeMethod(_desktop, "messageBox",
+ Q_RETURN_ARG(QVariant, result),
+ Q_ARG(QVariant, QVariant::fromValue(map)));
+ } else {
+ QQuickItem* tabletRoot = tablet->getTabletRoot();
+ invokeResult = QMetaObject::invokeMethod(tabletRoot, "messageBox",
+ Q_RETURN_ARG(QVariant, result),
+ Q_ARG(QVariant, QVariant::fromValue(map)));
+ }
if (!invokeResult) {
qWarning() << "Failed to create message box";
@@ -405,10 +416,21 @@ QQuickItem* OffscreenUi::createInputDialog(const Icon icon, const QString& title
map.insert("label", label);
map.insert("current", current);
QVariant result;
- bool invokeResult = QMetaObject::invokeMethod(_desktop, "inputDialog",
- Q_RETURN_ARG(QVariant, result),
- Q_ARG(QVariant, QVariant::fromValue(map)));
+ auto tabletScriptingInterface = DependencyManager::get();
+ TabletProxy* tablet = dynamic_cast(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
+
+ bool invokeResult;
+ if (tablet->getToolbarMode()) {
+ invokeResult = QMetaObject::invokeMethod(_desktop, "inputDialog",
+ Q_RETURN_ARG(QVariant, result),
+ Q_ARG(QVariant, QVariant::fromValue(map)));
+ } else {
+ QQuickItem* tabletRoot = tablet->getTabletRoot();
+ invokeResult = QMetaObject::invokeMethod(tabletRoot, "inputDialog",
+ Q_RETURN_ARG(QVariant, result),
+ Q_ARG(QVariant, QVariant::fromValue(map)));
+ }
if (!invokeResult) {
qWarning() << "Failed to create message box";
return nullptr;
@@ -422,10 +444,21 @@ QQuickItem* OffscreenUi::createCustomInputDialog(const Icon icon, const QString&
map.insert("title", title);
map.insert("icon", icon);
QVariant result;
- bool invokeResult = QMetaObject::invokeMethod(_desktop, "customInputDialog",
- Q_RETURN_ARG(QVariant, result),
- Q_ARG(QVariant, QVariant::fromValue(map)));
+ auto tabletScriptingInterface = DependencyManager::get();
+ TabletProxy* tablet = dynamic_cast(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
+ bool invokeResult;
+ if (tablet->getToolbarMode()) {
+ invokeResult = QMetaObject::invokeMethod(_desktop, "inputDialog",
+ Q_RETURN_ARG(QVariant, result),
+ Q_ARG(QVariant, QVariant::fromValue(map)));
+ } else {
+ QQuickItem* tabletRoot = tablet->getTabletRoot();
+ invokeResult = QMetaObject::invokeMethod(tabletRoot, "inputDialog",
+ Q_RETURN_ARG(QVariant, result),
+ Q_ARG(QVariant, QVariant::fromValue(map)));
+ }
+
if (!invokeResult) {
qWarning() << "Failed to create custom message box";
return nullptr;
@@ -569,9 +602,19 @@ private slots:
QString OffscreenUi::fileDialog(const QVariantMap& properties) {
QVariant buildDialogResult;
- bool invokeResult = QMetaObject::invokeMethod(_desktop, "fileDialog",
- Q_RETURN_ARG(QVariant, buildDialogResult),
- Q_ARG(QVariant, QVariant::fromValue(properties)));
+ bool invokeResult;
+ auto tabletScriptingInterface = DependencyManager::get();
+ TabletProxy* tablet = dynamic_cast(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
+ if (tablet->getToolbarMode()) {
+ invokeResult = QMetaObject::invokeMethod(_desktop, "fileDialog",
+ Q_RETURN_ARG(QVariant, buildDialogResult),
+ Q_ARG(QVariant, QVariant::fromValue(properties)));
+ } else {
+ QQuickItem* tabletRoot = tablet->getTabletRoot();
+ invokeResult = QMetaObject::invokeMethod(tabletRoot, "fileDialog",
+ Q_RETURN_ARG(QVariant, buildDialogResult),
+ Q_ARG(QVariant, QVariant::fromValue(properties)));
+ }
if (!invokeResult) {
qWarning() << "Failed to create file open dialog";
diff --git a/scripts/developer/utilities/audio/stats.qml b/scripts/developer/utilities/audio/Stats.qml
similarity index 93%
rename from scripts/developer/utilities/audio/stats.qml
rename to scripts/developer/utilities/audio/Stats.qml
index 346e5e3544..7f559ea664 100644
--- a/scripts/developer/utilities/audio/stats.qml
+++ b/scripts/developer/utilities/audio/Stats.qml
@@ -1,5 +1,5 @@
//
-// stats.qml
+// Stats.qml
// scripts/developer/utilities/audio
//
// Created by Zach Pomerantz on 9/22/2016
@@ -12,22 +12,21 @@ import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.3
+import "../../../../resources/qml/controls-uit" as HifiControls
+
Column {
id: stats
width: parent.width
- height: parent.height
property bool showGraphs: toggleGraphs.checked
- RowLayout {
+ Item {
width: parent.width
height: 30
- Button {
+ HifiControls.Button {
id: toggleGraphs
property bool checked: false
-
- Layout.alignment: Qt.AlignCenter
-
+ anchors.horizontalCenter: parent.horizontalCenter
text: checked ? "Hide graphs" : "Show graphs"
onClicked: function() { checked = !checked; }
}
@@ -35,11 +34,9 @@ Column {
Grid {
width: parent.width
- height: parent.height - 30
Column {
width: parent.width / 2
- height: parent.height
Section {
label: "Latency"
@@ -76,7 +73,6 @@ Column {
Column {
width: parent.width / 2
- height: parent.height
Section {
label: "Mixer (upstream)"
@@ -92,4 +88,3 @@ Column {
}
}
}
-
diff --git a/scripts/developer/utilities/audio/TabletStats.qml b/scripts/developer/utilities/audio/TabletStats.qml
new file mode 100644
index 0000000000..130b90f032
--- /dev/null
+++ b/scripts/developer/utilities/audio/TabletStats.qml
@@ -0,0 +1,89 @@
+//
+// TabletStats.qml
+// scripts/developer/utilities/audio
+//
+// Created by David Rowe on 3 Mar 2017.
+// Copyright 2017 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html
+//
+import QtQuick 2.5
+import QtQuick.Controls 1.4
+import QtQuick.Layouts 1.3
+
+import "../../../../resources/qml/styles-uit"
+
+Item {
+ id: dialog
+ width: 480
+ height: 720
+
+ HifiConstants { id: hifi }
+
+ Rectangle {
+ id: header
+ height: 90
+ anchors {
+ top: parent.top
+ left: parent.left
+ right: parent.right
+ }
+ z: 100
+
+ gradient: Gradient {
+ GradientStop {
+ position: 0
+ color: "#2b2b2b"
+ }
+
+ GradientStop {
+ position: 1
+ color: "#1e1e1e"
+ }
+ }
+
+ RalewayBold {
+ text: "Audio Interface Statistics"
+ size: 26
+ color: "#34a2c7"
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.left: parent.left
+ anchors.leftMargin: hifi.dimensions.contentMargin.x // ####### hifi is not defined
+ }
+ }
+
+ Rectangle {
+ id: main
+ anchors {
+ top: header.bottom
+ bottom: parent.bottom
+ left: parent.left
+ right: parent.right
+ }
+
+ gradient: Gradient {
+ GradientStop {
+ position: 0
+ color: "#2b2b2b"
+ }
+
+ GradientStop {
+ position: 1
+ color: "#0f212e"
+ }
+ }
+
+ Flickable {
+ id: scrollView
+ width: parent.width
+ height: parent.height
+ contentWidth: parent.width
+ contentHeight: stats.height
+
+ Stats {
+ id: stats
+ }
+ }
+ }
+}
diff --git a/scripts/developer/utilities/audio/stats.js b/scripts/developer/utilities/audio/stats.js
index 493271ac99..382e14df5f 100644
--- a/scripts/developer/utilities/audio/stats.js
+++ b/scripts/developer/utilities/audio/stats.js
@@ -9,17 +9,23 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
-var INITIAL_WIDTH = 400;
-var INITIAL_OFFSET = 50;
+if (HMD.active && !Settings.getValue("HUDUIEnabled")) {
+ var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
+ var qml = Script.resolvePath("TabletStats.qml");
+ tablet.loadQMLSource(qml);
+ Script.stop();
-// Set up the qml ui
-var qml = Script.resolvePath('stats.qml');
-var window = new OverlayWindow({
- title: 'Audio Interface Statistics',
- source: qml,
- width: 500, height: 520 // stats.qml may be too large for some screens
-});
-window.setPosition(INITIAL_OFFSET, INITIAL_OFFSET);
+} else {
+ var INITIAL_WIDTH = 400;
+ var INITIAL_OFFSET = 50;
-window.closed.connect(function() { Script.stop(); });
+ var qml = Script.resolvePath("Stats.qml");
+ var window = new OverlayWindow({
+ title: "Audio Interface Statistics",
+ source: qml,
+ width: 500, height: 520 // stats.qml may be too large for some screens
+ });
+ window.setPosition(INITIAL_OFFSET, INITIAL_OFFSET);
+ window.closed.connect(function () { Script.stop(); });
+}
diff --git a/scripts/system/audio.js b/scripts/system/audio.js
index 6e7e95d659..beeb8609d8 100644
--- a/scripts/system/audio.js
+++ b/scripts/system/audio.js
@@ -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);
diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js
index a64567fedb..1ccf443a00 100644
--- a/scripts/system/controllers/handControllerGrab.js
+++ b/scripts/system/controllers/handControllerGrab.js
@@ -13,8 +13,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
/* global getEntityCustomData, flatten, Xform, Script, Quat, Vec3, MyAvatar, Entities, Overlays, Settings,
- Reticle, Controller, Camera, Messages, Mat4, getControllerWorldLocation, getGrabPointSphereOffset, setGrabCommunications,
- Menu, HMD, isInEditMode */
+ Reticle, Controller, Camera, Messages, Mat4, getControllerWorldLocation, getGrabPointSphereOffset,
+ setGrabCommunications, Menu, HMD, isInEditMode */
/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */
(function() { // BEGIN LOCAL_SCOPE
@@ -28,7 +28,7 @@ Script.include("/~/system/libraries/controllers.js");
//
var WANT_DEBUG = false;
-var WANT_DEBUG_STATE = false;
+var WANT_DEBUG_STATE = true;
var WANT_DEBUG_SEARCH_NAME = null;
var FORCE_IGNORE_IK = false;
@@ -1027,6 +1027,7 @@ function MyController(hand) {
this.grabPointIntersectsEntity = false;
this.stylus = null;
this.homeButtonTouched = false;
+ this.editTriggered = false;
this.controllerJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ?
"_CONTROLLER_RIGHTHAND" :
@@ -1389,7 +1390,7 @@ function MyController(hand) {
}
var searchSphereLocation = Vec3.sum(distantPickRay.origin,
- Vec3.multiply(distantPickRay.direction, this.searchSphereDistance));
+ Vec3.multiply(distantPickRay.direction, this.searchSphereDistance));
this.searchSphereOn(searchSphereLocation, SEARCH_SPHERE_SIZE * this.searchSphereDistance,
(this.triggerSmoothedGrab() || this.secondarySqueezed()) ?
COLORS_GRAB_SEARCHING_FULL_SQUEEZE :
@@ -1709,6 +1710,10 @@ function MyController(hand) {
this.checkForUnexpectedChildren();
+ if (this.editTriggered) {
+ this.editTriggered = false;
+ }
+
if (this.triggerSmoothedReleased() && this.secondaryReleased()) {
this.waitForTriggerRelease = false;
}
@@ -2177,23 +2182,21 @@ function MyController(hand) {
return aDistance - bDistance;
});
entity = grabbableEntities[0];
- name = entityPropertiesCache.getProps(entity).name;
- this.grabbedThingID = entity;
- this.grabbedIsOverlay = false;
- if (this.entityWantsTrigger(entity)) {
- if (this.triggerSmoothedGrab()) {
- this.setState(STATE_NEAR_TRIGGER, "near trigger '" + name + "'");
- return;
+ if (!isInEditMode() || entity == HMD.tabletID) { // tablet is grabbable, even when editing
+ name = entityPropertiesCache.getProps(entity).name;
+ this.grabbedThingID = entity;
+ this.grabbedIsOverlay = false;
+ if (this.entityWantsTrigger(entity)) {
+ if (this.triggerSmoothedGrab()) {
+ this.setState(STATE_NEAR_TRIGGER, "near trigger '" + name + "'");
+ return;
+ }
} else {
- // potentialNearTriggerEntity = entity;
- }
- } else {
- // If near something grabbable, grab it!
- if ((this.triggerSmoothedGrab() || this.secondarySqueezed()) && nearGrabEnabled) {
- this.setState(STATE_NEAR_GRABBING, "near grab entity '" + name + "'");
- return;
- } else {
- // potentialNearGrabEntity = entity;
+ // If near something grabbable, grab it!
+ if ((this.triggerSmoothedGrab() || this.secondarySqueezed()) && nearGrabEnabled) {
+ this.setState(STATE_NEAR_GRABBING, "near grab entity '" + name + "'");
+ return;
+ }
}
}
}
@@ -2208,6 +2211,21 @@ function MyController(hand) {
}
}
+ if (isInEditMode()) {
+ this.searchIndicatorOn(rayPickInfo.searchRay);
+ if (this.triggerSmoothedGrab()) {
+ if (!this.editTriggered && rayPickInfo.entityID) {
+ Messages.sendLocalMessage("entityToolUpdates", JSON.stringify({
+ method: "selectEntity",
+ entityID: rayPickInfo.entityID
+ }));
+ }
+ this.editTriggered = true;
+ }
+ Reticle.setVisible(false);
+ return;
+ }
+
if (rayPickInfo.entityID) {
entity = rayPickInfo.entityID;
name = entityPropertiesCache.getProps(entity).name;
@@ -2277,12 +2295,12 @@ function MyController(hand) {
return false;
};
- this.handleLaserOnWebEntity = function(rayPickInfo) {
+ this.handleLaserOnWebEntity = function (rayPickInfo) {
var pointerEvent;
+
if (rayPickInfo.entityID && Entities.wantsHandControllerPointerEvents(rayPickInfo.entityID)) {
var entity = rayPickInfo.entityID;
- var props = entityPropertiesCache.getProps(entity);
- var name = props.name;
+ var name = entityPropertiesCache.getProps(entity).name;
if (Entities.keyboardFocusEntity != entity) {
Overlays.keyboardFocusOverlay = 0;
@@ -2293,7 +2311,7 @@ function MyController(hand) {
id: this.hand + 1, // 0 is reserved for hardware mouse
pos2D: projectOntoEntityXYPlane(entity, rayPickInfo.intersection),
pos3D: rayPickInfo.intersection,
- normal: rayPickInfo.normal,
+ normal: rayPickInfo.normal,
direction: rayPickInfo.searchRay.direction,
button: "None"
};
@@ -2325,12 +2343,13 @@ function MyController(hand) {
Entities.sendHoverOverEntity(entity, pointerEvent);
}
- if (this.triggerSmoothedGrab() && (!isEditing() || this.isTablet(entity))) {
+ if (this.triggerSmoothedGrab()) {
this.grabbedThingID = entity;
this.grabbedIsOverlay = false;
this.setState(STATE_ENTITY_LASER_TOUCHING, "begin touching entity '" + name + "'");
return true;
}
+
} else if (this.hoverEntity) {
pointerEvent = {
type: "Move",
@@ -2343,13 +2362,13 @@ function MyController(hand) {
return false;
};
- this.handleLaserOnWebOverlay = function(rayPickInfo) {
+ this.handleLaserOnWebOverlay = function (rayPickInfo) {
var pointerEvent;
- var overlay;
-
if (rayPickInfo.overlayID) {
- overlay = rayPickInfo.overlayID;
-
+ var overlay = rayPickInfo.overlayID;
+ if (Overlays.getProperty(overlay, "type") != "web3d") {
+ return false;
+ }
if (Overlays.keyboardFocusOverlay != overlay) {
Entities.keyboardFocusEntity = null;
Overlays.keyboardFocusOverlay = overlay;
@@ -3366,7 +3385,7 @@ function MyController(hand) {
entityPropertiesCache.addEntity(this.grabbedThingID);
- if (this.state == STATE_ENTITY_LASER_TOUCHING && !this.triggerSmoothedGrab()) {
+ if (this.state == STATE_ENTITY_LASER_TOUCHING && !this.triggerSmoothedGrab()) { // AJT:
this.setState(STATE_OFF, "released trigger");
return;
}
@@ -3663,6 +3682,8 @@ function MyController(hand) {
this.cleanup = function() {
this.release();
this.grabPointSphereOff();
+ this.hideStylus();
+ this.overlayLineOff();
};
this.thisHandIsParent = function(props) {
diff --git a/scripts/system/edit.js b/scripts/system/edit.js
index a440fec1ac..74080dbe09 100644
--- a/scripts/system/edit.js
+++ b/scripts/system/edit.js
@@ -1,7 +1,6 @@
"use strict";
-// newEditEntities.js
-// examples
+// edit.js
//
// Created by Brad Hefta-Gaub on 10/2/14.
// Persist toolbar by HRS 6/11/15.
@@ -13,6 +12,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
+/* global Script, SelectionDisplay, LightOverlayManager, CameraManager, Grid, GridTool, EntityListTool, Vec3, SelectionManager, Overlays, OverlayWebWindow, UserActivityLogger, Settings, Entities, Tablet, Toolbars, Messages, Menu, Camera, progressDialog, tooltip, MyAvatar, Quat, Controller, Clipboard, HMD, UndoStack, ParticleExplorerTool */
+
(function() { // BEGIN LOCAL_SCOPE
var HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
@@ -59,18 +60,13 @@ selectionManager.addEventListener(function () {
const KEY_P = 80; //Key code for letter p used for Parenting hotkey.
var DEGREES_TO_RADIANS = Math.PI / 180.0;
var RADIANS_TO_DEGREES = 180.0 / Math.PI;
-var epsilon = 0.001;
var MIN_ANGULAR_SIZE = 2;
var MAX_ANGULAR_SIZE = 45;
var allowLargeModels = true;
var allowSmallModels = true;
-var SPAWN_DISTANCE = 1;
var DEFAULT_DIMENSION = 0.20;
-var DEFAULT_TEXT_DIMENSION_X = 1.0;
-var DEFAULT_TEXT_DIMENSION_Y = 1.0;
-var DEFAULT_TEXT_DIMENSION_Z = 0.01;
var DEFAULT_DIMENSIONS = {
x: DEFAULT_DIMENSION,
@@ -85,7 +81,6 @@ var MENU_EASE_ON_FOCUS = "Ease Orientation on Focus";
var MENU_SHOW_LIGHTS_IN_EDIT_MODE = "Show Lights in Edit Mode";
var MENU_SHOW_ZONES_IN_EDIT_MODE = "Show Zones in Edit Mode";
-var SETTING_INSPECT_TOOL_ENABLED = "inspectToolEnabled";
var SETTING_AUTO_FOCUS_ON_SELECT = "autoFocusOnSelect";
var SETTING_EASE_ON_FOCUS = "cameraEaseOnFocus";
var SETTING_SHOW_LIGHTS_IN_EDIT_MODE = "showLightsInEditMode";
@@ -157,13 +152,13 @@ function hideMarketplace() {
marketplaceWindow.setURL("about:blank");
}
-function toggleMarketplace() {
- if (marketplaceWindow.visible) {
- hideMarketplace();
- } else {
- showMarketplace();
- }
-}
+// function toggleMarketplace() {
+// if (marketplaceWindow.visible) {
+// hideMarketplace();
+// } else {
+// showMarketplace();
+// }
+// }
var TOOLS_PATH = Script.resolvePath("assets/images/tools/");
@@ -176,8 +171,6 @@ var toolBar = (function () {
tablet = null;
function createNewEntity(properties) {
- Settings.setValue(EDIT_SETTING, false);
-
var dimensions = properties.dimensions ? properties.dimensions : DEFAULT_DIMENSIONS;
var position = getPositionToCreateEntity();
var entityID = null;
@@ -185,8 +178,12 @@ var toolBar = (function () {
position = grid.snapToSurface(grid.snapToGrid(position, false, dimensions), dimensions),
properties.position = position;
entityID = Entities.addEntity(properties);
+ if (properties.type == "ParticleEffect") {
+ selectParticleEntity(entityID);
+ }
} else {
- Window.notifyEditError("Can't create " + properties.type + ": " + properties.type + " would be out of bounds.");
+ Window.notifyEditError("Can't create " + properties.type + ": " +
+ properties.type + " would be out of bounds.");
}
selectionManager.clearSelections();
@@ -206,24 +203,63 @@ var toolBar = (function () {
}
}
+ var buttonHandlers = {}; // only used to tablet mode
+
function addButton(name, image, handler) {
- var imageUrl = TOOLS_PATH + image;
- var button = toolBar.addButton({
- objectName: name,
- imageURL: imageUrl,
- imageOffOut: 1,
- imageOffIn: 2,
- imageOnOut: 0,
- imageOnIn: 2,
- alpha: 0.9,
- visible: true
- });
- if (handler) {
- button.clicked.connect(function () {
- Script.setTimeout(handler, 100);
- });
+ buttonHandlers[name] = handler;
+ }
+
+ var SHAPE_TYPE_NONE = 0;
+ var SHAPE_TYPE_SIMPLE_HULL = 1;
+ var SHAPE_TYPE_SIMPLE_COMPOUND = 2;
+ var SHAPE_TYPE_STATIC_MESH = 3;
+ var DYNAMIC_DEFAULT = false;
+
+ function handleNewModelDialogResult(result) {
+ if (result) {
+ var url = result.textInput;
+ 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) {
+ createNewEntity({
+ type: "Model",
+ modelURL: url,
+ shapeType: shapeType,
+ dynamic: dynamic,
+ gravity: dynamic ? { x: 0, y: -10, z: 0 } : { x: 0, y: 0, z: 0 }
+ });
+ }
+ }
+ }
+
+ function fromQml(message) { // messages are {method, params}, like json-rpc. See also sendToQml.
+ var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
+ tablet.popFromStack();
+ switch (message.method) {
+ case "newModelDialogAdd":
+ handleNewModelDialogResult(message.params);
+ break;
+ case "newEntityButtonClicked":
+ buttonHandlers[message.params.buttonName]();
+ break;
}
- return button;
}
function initialize() {
@@ -240,101 +276,40 @@ var toolBar = (function () {
}
});
-
- if (Settings.getValue("HUDUIEnabled")) {
- systemToolbar = Toolbars.getToolbar(SYSTEM_TOOLBAR);
- activeButton = systemToolbar.addButton({
- objectName: EDIT_TOGGLE_BUTTON,
- imageURL: TOOLS_PATH + "edit.svg",
- visible: true,
- alpha: 0.9,
- defaultState: 1
- });
- } else {
- tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
- activeButton = tablet.addButton({
- icon: "icons/tablet-icons/edit-i.svg",
- activeIcon: "icons/tablet-icons/edit-a.svg",
- text: "EDIT",
- sortOrder: 10
- });
- }
+ tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
+ activeButton = tablet.addButton({
+ icon: "icons/tablet-icons/edit-i.svg",
+ activeIcon: "icons/tablet-icons/edit-a.svg",
+ text: "EDIT",
+ sortOrder: 10
+ });
+ tablet.screenChanged.connect(function (type, url) {
+ if (isActive && (type !== "QML" || url !== "Edit.qml")) {
+ that.toggle();
+ }
+ });
+ tablet.fromQml.connect(fromQml);
activeButton.clicked.connect(function() {
that.toggle();
});
- toolBar = Toolbars.getToolbar(EDIT_TOOLBAR);
- toolBar.writeProperty("shown", false);
- addButton("openAssetBrowserButton","assets-01.svg",function(){
+ addButton("openAssetBrowserButton", "assets-01.svg", function(){
Window.showAssetServer();
- })
+ });
addButton("newModelButton", "model-01.svg", function () {
- 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 result = Window.customPrompt({
- textInput: {
- label: "Model URL"
- },
- 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"
- }
- });
- if (result) {
- var url = result.textInput;
- 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) {
- createNewEntity({
- type: "Model",
- modelURL: url,
- shapeType: shapeType,
- dynamic: dynamic,
- gravity: dynamic ? { x: 0, y: -10, z: 0 } : { x: 0, y: 0, z: 0 }
- });
- }
- }
+ // tablet version of new-model dialog
+ var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
+ tablet.pushOntoStack("NewModelDialog.qml");
});
addButton("newCubeButton", "cube-01.svg", function () {
@@ -456,10 +431,12 @@ var toolBar = (function () {
entityListTool.clearEntityList();
};
-
that.toggle = function () {
that.setActive(!isActive);
activeButton.editProperties({isActive: isActive});
+ if (!isActive) {
+ tablet.gotoHomeScreen();
+ }
};
that.setActive = function (active) {
@@ -489,6 +466,8 @@ var toolBar = (function () {
cameraManager.disable();
selectionDisplay.triggerMapping.disable();
} else {
+ var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
+ tablet.loadQMLSource("Edit.qml");
UserActivityLogger.enabledEdit();
entityListTool.setVisible(true);
gridTool.setVisible(true);
@@ -499,13 +478,6 @@ var toolBar = (function () {
// everybody else to think that Interface has lost focus overall. fogbugzid:558
// Window.setFocus();
}
- // Sets visibility of tool buttons, excluding the power button
- toolBar.writeProperty("shown", active);
- var visible = toolBar.readProperty("visible");
- if (active && !visible) {
- toolBar.writeProperty("shown", false);
- toolBar.writeProperty("shown", true);
- }
lightOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_LIGHTS_IN_EDIT_MODE));
Entities.setDrawZoneBoundaries(isActive && Menu.isOptionChecked(MENU_SHOW_ZONES_IN_EDIT_MODE));
};
@@ -642,7 +614,6 @@ var idleMouseTimerId = null;
var CLICK_TIME_THRESHOLD = 500 * 1000; // 500 ms
var CLICK_MOVE_DISTANCE_THRESHOLD = 20;
var IDLE_MOUSE_TIMEOUT = 200;
-var DEFAULT_ENTITY_DRAG_DROP_DISTANCE = 2.0;
var lastMouseMoveEvent = null;
@@ -772,6 +743,12 @@ function mouseClickEvent(event) {
orientation = MyAvatar.orientation;
intersection = rayPlaneIntersection(pickRay, P, Quat.getFront(orientation));
+ if (event.isShifted) {
+ particleExplorerTool.destroyWebView();
+ }
+ if (properties.type !== "ParticleEffect") {
+ particleExplorerTool.destroyWebView();
+ }
if (!event.isShifted) {
selectionManager.setSelections([foundEntity]);
@@ -1297,11 +1274,11 @@ function getPositionToCreateEntity() {
var position = Vec3.sum(MyAvatar.position, Vec3.multiply(direction, distance));
if (Camera.mode === "entity" || Camera.mode === "independent") {
- position = Vec3.sum(Camera.position, Vec3.multiply(Quat.getFront(Camera.orientation), distance))
+ position = Vec3.sum(Camera.position, Vec3.multiply(Quat.getFront(Camera.orientation), distance));
}
position.y += 0.5;
if (position.x > HALF_TREE_SCALE || position.y > HALF_TREE_SCALE || position.z > HALF_TREE_SCALE) {
- return null
+ return null;
}
return position;
}
@@ -1315,11 +1292,11 @@ function getPositionToImportEntity() {
var position = Vec3.sum(MyAvatar.position, Vec3.multiply(direction, longest));
if (Camera.mode === "entity" || Camera.mode === "independent") {
- position = Vec3.sum(Camera.position, Vec3.multiply(Quat.getFront(Camera.orientation), longest))
+ position = Vec3.sum(Camera.position, Vec3.multiply(Quat.getFront(Camera.orientation), longest));
}
if (position.x > HALF_TREE_SCALE || position.y > HALF_TREE_SCALE || position.z > HALF_TREE_SCALE) {
- return null
+ return null;
}
return position;
@@ -1527,11 +1504,11 @@ var ServerScriptStatusMonitor = function(entityID, statusCallback) {
Entities.getServerScriptStatus(entityID, onStatusReceived);
}
}, 1000);
- };
+ }
};
self.stop = function() {
self.active = false;
- }
+ };
Entities.getServerScriptStatus(entityID, onStatusReceived);
};
@@ -1539,11 +1516,9 @@ var ServerScriptStatusMonitor = function(entityID, statusCallback) {
var PropertiesTool = function (opts) {
var that = {};
- var webView = new OverlayWebWindow({
- title: 'Entity Properties',
- source: ENTITY_PROPERTIES_URL,
- toolWindow: true
- });
+ var webView = null;
+ webView = Tablet.getTablet("com.highfidelity.interface.tablet.system");
+ webView.setVisible = function(value) {};
var visible = false;
@@ -1562,7 +1537,7 @@ var PropertiesTool = function (opts) {
function updateScriptStatus(info) {
info.type = "server_script_status";
webView.emitScriptEvent(JSON.stringify(info));
- };
+ }
function resetScriptStatus() {
updateScriptStatus({
@@ -1621,7 +1596,7 @@ var PropertiesTool = function (opts) {
data = JSON.parse(data);
}
catch(e) {
- print('Edit.js received web event that was not valid json.')
+ print('Edit.js received web event that was not valid json.');
return;
}
var i, properties, dY, diff, newPosition;
@@ -1639,15 +1614,15 @@ var PropertiesTool = function (opts) {
for (i = 0; i < selectionManager.selections.length; i++) {
Entities.editEntity(selectionManager.selections[i], properties);
}
- } else {
+ } else if (data.properties) {
if (data.properties.dynamic === false) {
// this object is leaving dynamic, so we zero its velocities
- data.properties["velocity"] = {
+ data.properties.velocity = {
x: 0,
y: 0,
z: 0
};
- data.properties["angularVelocity"] = {
+ data.properties.angularVelocity = {
x: 0,
y: 0,
z: 0
@@ -1960,6 +1935,21 @@ var showMenuItem = propertyMenu.addMenuItem("Show in Marketplace");
var propertiesTool = new PropertiesTool();
var particleExplorerTool = new ParticleExplorerTool();
var selectedParticleEntity = 0;
+
+function selectParticleEntity(entityID) {
+ var properties = Entities.getEntityProperties(entityID);
+ var particleData = {
+ messageType: "particle_settings",
+ currentProperties: properties
+ };
+ particleExplorerTool.destroyWebView();
+ particleExplorerTool.createWebView();
+
+ selectedParticleEntity = entityID;
+ particleExplorerTool.setActiveParticleEntity(entityID);
+ particleExplorerTool.webView.emitScriptEvent(JSON.stringify(particleData));
+}
+
entityListTool.webView.webEventReceived.connect(function (data) {
data = JSON.parse(data);
if(data.type === 'parent') {
@@ -1975,22 +1965,7 @@ entityListTool.webView.webEventReceived.connect(function (data) {
return;
}
// Destroy the old particles web view first
- particleExplorerTool.destroyWebView();
- particleExplorerTool.createWebView();
- var properties = Entities.getEntityProperties(ids[0]);
- var particleData = {
- messageType: "particle_settings",
- currentProperties: properties
- };
- selectedParticleEntity = ids[0];
- particleExplorerTool.setActiveParticleEntity(ids[0]);
-
- particleExplorerTool.webView.webEventReceived.connect(function (data) {
- data = JSON.parse(data);
- if (data.messageType === "page_loaded") {
- particleExplorerTool.webView.emitScriptEvent(JSON.stringify(particleData));
- }
- });
+ selectParticleEntity(ids[0]);
} else {
selectedParticleEntity = 0;
particleExplorerTool.destroyWebView();
diff --git a/scripts/system/generalSettings.js b/scripts/system/generalSettings.js
index 0a9fc823ae..7d97f13757 100644
--- a/scripts/system/generalSettings.js
+++ b/scripts/system/generalSettings.js
@@ -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);
diff --git a/scripts/system/goto.js b/scripts/system/goto.js
index 0e09ea3d79..d364bf579e 100644
--- a/scripts/system/goto.js
+++ b/scripts/system/goto.js
@@ -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);
diff --git a/scripts/system/help.js b/scripts/system/help.js
index 5a1b712fb5..3923b922fc 100644
--- a/scripts/system/help.js
+++ b/scripts/system/help.js
@@ -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) {
diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css
index 251d0a2d75..06a60b5405 100644
--- a/scripts/system/html/css/edit-style.css
+++ b/scripts/system/html/css/edit-style.css
@@ -871,6 +871,7 @@ textarea:enabled[scrolling="true"]::-webkit-resizer {
float: right;
margin-right: 0;
background-color: #ff0000;
+ min-width: 90px;
}
#entity-list {
diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html
index 5022dbd6a6..35accdd0df 100644
--- a/scripts/system/html/entityProperties.html
+++ b/scripts/system/html/entityProperties.html
@@ -77,7 +77,7 @@
-
+