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 @@
- +
- \ No newline at end of file + diff --git a/scripts/system/particle_explorer/particleExplorer.js b/scripts/system/particle_explorer/particleExplorer.js index 4fd0978a84..e0987ecd09 100644 --- a/scripts/system/particle_explorer/particleExplorer.js +++ b/scripts/system/particle_explorer/particleExplorer.js @@ -32,6 +32,8 @@ var gui = null; var settings = new Settings(); var updateInterval; +var active = false; + var currentInputField; var storedController; //CHANGE TO WHITELIST @@ -358,9 +360,25 @@ function listenForSettingsUpdates() { settings[key] = value; }); - loadGUI(); - } + if (gui) { + manuallyUpdateDisplay(); + } else { + loadGUI(); + } + if (!active) { + // gui.toggleHide(); + gui.closed = false; + } + active = true; + } else if (data.messageType === "particle_close") { + // none of this seems to work. + // if (active) { + // gui.toggleHide(); + // } + active = false; + gui.closed = true; + } }); } @@ -505,4 +523,4 @@ function registerDOMElementsForListenerBlocking() { }); }); }); -} \ No newline at end of file +} diff --git a/scripts/system/particle_explorer/particleExplorerTool.js b/scripts/system/particle_explorer/particleExplorerTool.js index 8a28445c33..b3db475ab0 100644 --- a/scripts/system/particle_explorer/particleExplorerTool.js +++ b/scripts/system/particle_explorer/particleExplorerTool.js @@ -18,26 +18,21 @@ ParticleExplorerTool = function() { var that = {}; that.createWebView = function() { - var url = PARTICLE_EXPLORER_HTML_URL; - that.webView = new OverlayWebWindow({ - title: 'Particle Explorer', - source: url, - toolWindow: true - }); - - that.webView.setVisible(true); + that.webView = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + that.webView.setVisible = function(value) {}; that.webView.webEventReceived.connect(that.webEventReceived); } - that.destroyWebView = function() { if (!that.webView) { return; } - - that.webView.close(); - that.webView = null; that.activeParticleEntity = 0; + + var messageData = { + messageType: "particle_close" + }; + that.webView.emitScriptEvent(JSON.stringify(messageData)); } that.webEventReceived = function(data) { @@ -51,8 +46,5 @@ ParticleExplorerTool = function() { that.activeParticleEntity = id; } - return that; - - -}; \ No newline at end of file +}; diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 8f918c9cb2..b9dfc43f9a 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -191,12 +191,12 @@ function resetButtons(pathStillSnapshot, pathAnimatedSnapshot, notify) { if (clearOverlayWhenMoving) { MyAvatar.setClearOverlayWhenMoving(true); // not until after the share dialog } + HMD.openTablet(); } function processingGif() { // show hud Reticle.visible = reticleVisible; - button.clicked.disconnect(onClicked); buttonConnected = false; // show overlays if they were on @@ -211,8 +211,10 @@ Window.snapshotShared.connect(snapshotShared); Window.processingGif.connect(processingGif); Script.scriptEnding.connect(function () { - button.clicked.disconnect(onClicked); - buttonConnected = false; + if (buttonConnected) { + button.clicked.disconnect(onClicked); + buttonConnected = false; + } if (tablet) { tablet.removeButton(button); } diff --git a/scripts/system/tablet-ui/tabletUI.js b/scripts/system/tablet-ui/tabletUI.js index fd73578328..263c8822df 100644 --- a/scripts/system/tablet-ui/tabletUI.js +++ b/scripts/system/tablet-ui/tabletUI.js @@ -12,21 +12,47 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/* global Script, HMD, WebTablet, UIWebTablet, UserActivityLogger, Settings, Entities, Messages, Tablet, Overlays, MyAvatar */ +/* global Script, HMD, WebTablet, UIWebTablet, UserActivityLogger, Settings, Entities, Messages, Tablet, Overlays, + MyAvatar, Menu */ (function() { // BEGIN LOCAL_SCOPE var tabletShown = false; - var tabletLocation = null; + var tabletRezzed = false; var activeHand = null; + var DEFAULT_WIDTH = 0.4375; + var DEFAULT_TABLET_SCALE = 100; + var preMakeTime = Date.now(); + var validCheckTime = Date.now(); + var debugTablet = false; + UIWebTablet = null; Script.include("../libraries/WebTablet.js"); - function showTabletUI() { - tabletShown = true; - print("show tablet-ui"); + function tabletIsValid() { + if (!UIWebTablet) { + return false; + } + if (UIWebTablet.tabletIsOverlay && Overlays.getProperty(HMD.tabletID, "type") != "model") { + if (debugTablet) { + print("TABLET is invalid due to frame: " + JSON.stringify(Overlays.getProperty(HMD.tabletID, "type"))); + } + return false; + } + if (Overlays.getProperty(HMD.homeButtonID, "type") != "sphere" || + Overlays.getProperty(HMD.tabletScreenID, "type") != "web3d") { + if (debugTablet) { + print("TABLET is invalid due to other"); + } + return false; + } + return true; + } - var DEFAULT_WIDTH = 0.4375; - var DEFAULT_TABLET_SCALE = 100; + + function rezTablet() { + if (debugTablet) { + print("TABLET rezzing"); + } var toolbarMode = Tablet.getTablet("com.highfidelity.interface.tablet.system").toolbarMode; var TABLET_SCALE = DEFAULT_TABLET_SCALE; if (toolbarMode) { @@ -34,37 +60,98 @@ } else { TABLET_SCALE = Settings.getValue("hmdTabletScale") || DEFAULT_TABLET_SCALE; } - UIWebTablet = new WebTablet("qml/hifi/tablet/TabletRoot.qml", DEFAULT_WIDTH * (TABLET_SCALE / 100), null, activeHand, true); + + UIWebTablet = new WebTablet("qml/hifi/tablet/TabletRoot.qml", + DEFAULT_WIDTH * (TABLET_SCALE / 100), + null, activeHand, true); UIWebTablet.register(); HMD.tabletID = UIWebTablet.tabletEntityID; HMD.homeButtonID = UIWebTablet.homeButtonID; HMD.tabletScreenID = UIWebTablet.webOverlayID; + + tabletRezzed = true; + } + + function showTabletUI() { + tabletShown = true; + + if (!tabletRezzed) { + rezTablet(false); + } + + if (UIWebTablet && tabletRezzed) { + if (debugTablet) { + print("TABLET in showTabletUI, already rezzed"); + } + var tabletProperties = {}; + UIWebTablet.calculateTabletAttachmentProperties(activeHand, true, tabletProperties); + tabletProperties.visible = true; + if (UIWebTablet.tabletIsOverlay) { + Overlays.editOverlay(HMD.tabletID, tabletProperties); + } + Overlays.editOverlay(HMD.homeButtonID, { visible: true }); + Overlays.editOverlay(HMD.tabletScreenID, { visible: true }); + Overlays.editOverlay(HMD.tabletScreenID, { maxFPS: 90 }); + } } function hideTabletUI() { tabletShown = false; - print("hide tablet-ui"); + if (!UIWebTablet) { + return; + } + + if (UIWebTablet.tabletIsOverlay) { + if (debugTablet) { + print("TABLET hide"); + } + if (Settings.getValue("tabletVisibleToOthers")) { + closeTabletUI(); + } else { + // Overlays.editOverlay(HMD.tabletID, { localPosition: { x: -1000, y: 0, z:0 } }); + Overlays.editOverlay(HMD.tabletID, { visible: false }); + Overlays.editOverlay(HMD.homeButtonID, { visible: false }); + Overlays.editOverlay(HMD.tabletScreenID, { visible: false }); + Overlays.editOverlay(HMD.tabletScreenID, { maxFPS: 1 }); + } + } else { + closeTabletUI(); + } + } + + function closeTabletUI() { + tabletShown = false; if (UIWebTablet) { if (UIWebTablet.onClose) { UIWebTablet.onClose(); } - tabletLocation = UIWebTablet.getLocation(); + if (debugTablet) { + print("TABLET close"); + } UIWebTablet.unregister(); UIWebTablet.destroy(); UIWebTablet = null; HMD.tabletID = null; HMD.homeButtonID = null; HMD.tabletScreenID = null; + } else if (debugTablet) { + print("TABLET closeTabletUI, UIWebTablet is null"); } + tabletRezzed = false; } + function updateShowTablet() { + var MSECS_PER_SEC = 1000.0; + var now = Date.now(); // close the WebTablet if it we go into toolbar mode. var toolbarMode = Tablet.getTablet("com.highfidelity.interface.tablet.system").toolbarMode; + var visibleToOthers = Settings.getValue("tabletVisibleToOthers"); + if (tabletShown && toolbarMode) { - hideTabletUI(); + closeTabletUI(); HMD.closeTablet(); return; } @@ -78,19 +165,48 @@ tablet.updateAudioBar(currentMicLevel); } - if (tabletShown && UIWebTablet && Overlays.getOverlayType(UIWebTablet.webOverlayID) != "web3d") { - // when we switch domains, the tablet entity gets destroyed and recreated. this causes - // the overlay to be deleted, but not recreated. If the overlay is deleted for this or any - // other reason, close the tablet. - hideTabletUI(); - HMD.closeTablet(); - } else if (HMD.showTablet && !tabletShown && !toolbarMode) { - UserActivityLogger.openedTablet(Settings.getValue("tabletVisibleToOthers")); + if (validCheckTime - now > MSECS_PER_SEC) { + validCheckTime = now; + if (tabletRezzed && UIWebTablet && !tabletIsValid()) { + // when we switch domains, the tablet entity gets destroyed and recreated. this causes + // the overlay to be deleted, but not recreated. If the overlay is deleted for this or any + // other reason, close the tablet. + closeTabletUI(); + HMD.closeTablet(); + if (debugTablet) { + print("TABLET autodestroying"); + } + } + } + + + if (HMD.showTablet && !tabletShown && !toolbarMode) { + UserActivityLogger.openedTablet(visibleToOthers); showTabletUI(); } else if (!HMD.showTablet && tabletShown) { UserActivityLogger.closedTablet(); - hideTabletUI(); + if (visibleToOthers) { + closeTabletUI(); + } else { + hideTabletUI(); + } } + + // if the tablet is an overlay, attempt to pre-create it and then hide it so that when it's + // summoned, it will appear quickly. + if (!toolbarMode && !visibleToOthers) { + if (now - preMakeTime > MSECS_PER_SEC) { + preMakeTime = now; + if (!tabletIsValid()) { + closeTabletUI(); + rezTablet(false); + tabletShown = false; + } else if (!tabletShown) { + hideTabletUI(); + } + } + } + } function toggleHand(channel, hand, senderUUID, localOnly) { diff --git a/scripts/system/tablet-users.js b/scripts/system/tablet-users.js index 8e89ac74b7..800f8bbe6f 100644 --- a/scripts/system/tablet-users.js +++ b/scripts/system/tablet-users.js @@ -67,7 +67,6 @@ } function onWebEventReceived(event) { - print("Script received a web event, its type is " + typeof event); if (typeof event === "string") { event = JSON.parse(event); } @@ -115,6 +114,9 @@ tablet.screenChanged.connect(onScreenChanged); function cleanup() { + if (onUsersScreen) { + tablet.gotoHomeScreen(); + } button.clicked.disconnect(onClicked); tablet.removeButton(button); } diff --git a/scripts/system/users.js b/scripts/system/users.js deleted file mode 100644 index 480b9f07a2..0000000000 --- a/scripts/system/users.js +++ /dev/null @@ -1,1281 +0,0 @@ -"use strict"; - -// -// users.js -// examples -// -// Created by David Rowe on 9 Mar 2015. -// 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 -// -/*globals HMD, Toolbars, Script, Menu, Overlays, Tablet, Controller, Settings, OverlayWebWindow, Account, GlobalServices */ - -(function() { // BEGIN LOCAL_SCOPE -var button; -var buttonName = "USERS"; -var toolBar = null; -var tablet = null; - -var MENU_ITEM = "Users Online"; - -if (Settings.getValue("HUDUIEnabled")) { - toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system"); - button = toolBar.addButton({ - objectName: buttonName, - imageURL: Script.resolvePath("assets/images/tools/people.svg"), - visible: true, - alpha: 0.9 - }); -} else { - tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - button = tablet.addButton({ - icon: "icons/tablet-icons/users-i.svg", - text: "USERS", - isActive: Menu.isOptionChecked(MENU_ITEM), - sortOrder: 11 - }); -} - - -function onClicked() { - Menu.setIsOptionChecked(MENU_ITEM, !Menu.isOptionChecked(MENU_ITEM)); - button.editProperties({isActive: Menu.isOptionChecked(MENU_ITEM)}); -} -button.clicked.connect(onClicked); - -// resolve these paths immediately -var MIN_MAX_BUTTON_SVG = Script.resolvePath("assets/images/tools/min-max-toggle.svg"); -var BASE_URL = Script.resolvePath("assets/images/tools/"); - -var PopUpMenu = function (properties) { - var value = properties.value, - promptOverlay, - valueOverlay, - buttonOverlay, - optionOverlays = [], - isDisplayingOptions = false, - OPTION_MARGIN = 4, - - MIN_MAX_BUTTON_SVG_WIDTH = 17.1, - MIN_MAX_BUTTON_SVG_HEIGHT = 32.5, - MIN_MAX_BUTTON_WIDTH = 14, - MIN_MAX_BUTTON_HEIGHT = MIN_MAX_BUTTON_WIDTH; - - function positionDisplayOptions() { - var y, - i; - - y = properties.y - (properties.values.length - 1) * properties.lineHeight - OPTION_MARGIN; - - for (i = 0; i < properties.values.length; i += 1) { - Overlays.editOverlay(optionOverlays[i], { - y: y - }); - y += properties.lineHeight; - } - } - - function showDisplayOptions() { - var i, - yOffScreen = Controller.getViewportDimensions().y; - - for (i = 0; i < properties.values.length; i += 1) { - optionOverlays[i] = Overlays.addOverlay("text", { - x: properties.x + properties.promptWidth, - y: yOffScreen, - width: properties.width - properties.promptWidth, - height: properties.textHeight + OPTION_MARGIN, // Only need to add margin at top to balance descenders - topMargin: OPTION_MARGIN, - leftMargin: OPTION_MARGIN, - color: properties.optionColor, - alpha: properties.optionAlpha, - backgroundColor: properties.popupBackgroundColor, - backgroundAlpha: properties.popupBackgroundAlpha, - text: properties.displayValues[i], - font: properties.font, - visible: true - }); - } - - positionDisplayOptions(); - - isDisplayingOptions = true; - } - - function deleteDisplayOptions() { - var i; - - for (i = 0; i < optionOverlays.length; i += 1) { - Overlays.deleteOverlay(optionOverlays[i]); - } - - isDisplayingOptions = false; - } - - function handleClick(overlay) { - var clicked = false, - i; - - if (overlay === valueOverlay || overlay === buttonOverlay) { - showDisplayOptions(); - return true; - } - - if (isDisplayingOptions) { - for (i = 0; i < optionOverlays.length; i += 1) { - if (overlay === optionOverlays[i]) { - value = properties.values[i]; - Overlays.editOverlay(valueOverlay, { - text: properties.displayValues[i] - }); - clicked = true; - } - } - - deleteDisplayOptions(); - } - - return clicked; - } - - function updatePosition(x, y) { - properties.x = x; - properties.y = y; - Overlays.editOverlay(promptOverlay, { - x: x, - y: y - }); - Overlays.editOverlay(valueOverlay, { - x: x + properties.promptWidth, - y: y - OPTION_MARGIN - }); - Overlays.editOverlay(buttonOverlay, { - x: x + properties.width - MIN_MAX_BUTTON_WIDTH - 1, - y: y - OPTION_MARGIN + 1 - }); - if (isDisplayingOptions) { - positionDisplayOptions(); - } - } - - function setVisible(visible) { - Overlays.editOverlay(promptOverlay, { - visible: visible - }); - Overlays.editOverlay(valueOverlay, { - visible: visible - }); - Overlays.editOverlay(buttonOverlay, { - visible: visible - }); - } - - function tearDown() { - Overlays.deleteOverlay(promptOverlay); - Overlays.deleteOverlay(valueOverlay); - Overlays.deleteOverlay(buttonOverlay); - if (isDisplayingOptions) { - deleteDisplayOptions(); - } - } - - function getValue() { - return value; - } - - function setValue(newValue) { - var index; - - index = properties.values.indexOf(newValue); - if (index !== -1) { - value = newValue; - Overlays.editOverlay(valueOverlay, { - text: properties.displayValues[index] - }); - } - } - - promptOverlay = Overlays.addOverlay("text", { - x: properties.x, - y: properties.y, - width: properties.promptWidth, - height: properties.textHeight, - topMargin: 0, - leftMargin: 0, - color: properties.promptColor, - alpha: properties.promptAlpha, - backgroundColor: properties.promptBackgroundColor, - backgroundAlpha: properties.promptBackgroundAlpha, - text: properties.prompt, - font: properties.font, - visible: properties.visible - }); - - valueOverlay = Overlays.addOverlay("text", { - x: properties.x + properties.promptWidth, - y: properties.y, - width: properties.width - properties.promptWidth, - height: properties.textHeight + OPTION_MARGIN, // Only need to add margin at top to balance descenders - topMargin: OPTION_MARGIN, - leftMargin: OPTION_MARGIN, - color: properties.optionColor, - alpha: properties.optionAlpha, - backgroundColor: properties.optionBackgroundColor, - backgroundAlpha: properties.optionBackgroundAlpha, - text: properties.displayValues[properties.values.indexOf(value)], - font: properties.font, - visible: properties.visible - }); - - buttonOverlay = Overlays.addOverlay("image", { - x: properties.x + properties.width - MIN_MAX_BUTTON_WIDTH - 1, - y: properties.y, - width: MIN_MAX_BUTTON_WIDTH, - height: MIN_MAX_BUTTON_HEIGHT, - imageURL: MIN_MAX_BUTTON_SVG, - subImage: { - x: 0, - y: 0, - width: MIN_MAX_BUTTON_SVG_WIDTH, - height: MIN_MAX_BUTTON_SVG_HEIGHT / 2 - }, - //color: properties.buttonColor, - alpha: properties.buttonAlpha, - visible: properties.visible - }); - - return { - updatePosition: updatePosition, - setVisible: setVisible, - handleClick: handleClick, - tearDown: tearDown, - getValue: getValue, - setValue: setValue - }; -}; - -var usersWindow = (function () { - - var WINDOW_WIDTH = 260, - WINDOW_MARGIN = 12, - WINDOW_BASE_MARGIN = 24, // A little less is needed in order look correct - WINDOW_FONT = { - size: 12 - }, - WINDOW_FOREGROUND_COLOR = { - red: 240, - green: 240, - blue: 240 - }, - WINDOW_FOREGROUND_ALPHA = 0.95, - WINDOW_HEADING_COLOR = { - red: 180, - green: 180, - blue: 180 - }, - WINDOW_HEADING_ALPHA = 0.95, - WINDOW_BACKGROUND_COLOR = { - red: 80, - green: 80, - blue: 80 - }, - WINDOW_BACKGROUND_ALPHA = 0.8, - windowPane, - windowHeading, - - // Margin on the left and right side of the window to keep - // it from getting too close to the edge of the screen which - // is unclickable. - WINDOW_MARGIN_X = 20, - - // Window border is similar to that of edit.js. - WINDOW_MARGIN_HALF = WINDOW_MARGIN / 2, - WINDOW_BORDER_WIDTH = WINDOW_WIDTH + 2 * WINDOW_MARGIN_HALF, - WINDOW_BORDER_TOP_MARGIN = 2 * WINDOW_MARGIN_HALF, - WINDOW_BORDER_BOTTOM_MARGIN = WINDOW_MARGIN_HALF, - WINDOW_BORDER_LEFT_MARGIN = WINDOW_MARGIN_HALF, - WINDOW_BORDER_RADIUS = 4, - WINDOW_BORDER_COLOR = { red: 255, green: 255, blue: 255 }, - WINDOW_BORDER_ALPHA = 0.5, - windowBorder, - - MIN_MAX_BUTTON_SVG = BASE_URL + "min-max-toggle.svg", - MIN_MAX_BUTTON_SVG_WIDTH = 17.1, - MIN_MAX_BUTTON_SVG_HEIGHT = 32.5, - MIN_MAX_BUTTON_WIDTH = 14, - MIN_MAX_BUTTON_HEIGHT = MIN_MAX_BUTTON_WIDTH, - MIN_MAX_BUTTON_COLOR = { - red: 255, - green: 255, - blue: 255 - }, - MIN_MAX_BUTTON_ALPHA = 0.9, - minimizeButton, - SCROLLBAR_BACKGROUND_WIDTH = 12, - SCROLLBAR_BACKGROUND_COLOR = { - red: 70, - green: 70, - blue: 70 - }, - SCROLLBAR_BACKGROUND_ALPHA = 0.8, - scrollbarBackground, - SCROLLBAR_BAR_MIN_HEIGHT = 5, - SCROLLBAR_BAR_COLOR = { - red: 170, - green: 170, - blue: 170 - }, - SCROLLBAR_BAR_ALPHA = 0.8, - SCROLLBAR_BAR_SELECTED_ALPHA = 0.95, - scrollbarBar, - scrollbarBackgroundHeight, - scrollbarBarHeight, - FRIENDS_BUTTON_SPACER = 6, // Space before add/remove friends button - FRIENDS_BUTTON_SVG = BASE_URL + "add-remove-friends.svg", - FRIENDS_BUTTON_SVG_WIDTH = 107, - FRIENDS_BUTTON_SVG_HEIGHT = 27, - FRIENDS_BUTTON_WIDTH = FRIENDS_BUTTON_SVG_WIDTH, - FRIENDS_BUTTON_HEIGHT = FRIENDS_BUTTON_SVG_HEIGHT, - FRIENDS_BUTTON_COLOR = { - red: 225, - green: 225, - blue: 225 - }, - FRIENDS_BUTTON_ALPHA = 0.95, - FRIENDS_WINDOW_URL = "https://metaverse.highfidelity.com/user/friends", - FRIENDS_WINDOW_WIDTH = 290, - FRIENDS_WINDOW_HEIGHT = 500, - FRIENDS_WINDOW_TITLE = "Add/Remove Friends", - friendsButton, - friendsWindow, - - OPTION_BACKGROUND_COLOR = { - red: 60, - green: 60, - blue: 60 - }, - OPTION_BACKGROUND_ALPHA = 0.1, - - DISPLAY_SPACER = 12, // Space before display control - DISPLAY_PROMPT = "Show me:", - DISPLAY_PROMPT_WIDTH = 60, - DISPLAY_EVERYONE = "everyone", - DISPLAY_FRIENDS = "friends", - DISPLAY_VALUES = [DISPLAY_EVERYONE, DISPLAY_FRIENDS], - DISPLAY_DISPLAY_VALUES = DISPLAY_VALUES, - DISPLAY_OPTIONS_BACKGROUND_COLOR = { - red: 120, - green: 120, - blue: 120 - }, - DISPLAY_OPTIONS_BACKGROUND_ALPHA = 0.9, - displayControl, - - VISIBILITY_SPACER = 6, // Space before visibility control - VISIBILITY_PROMPT = "Visible to:", - VISIBILITY_PROMPT_WIDTH = 60, - VISIBILITY_ALL = "all", - VISIBILITY_FRIENDS = "friends", - VISIBILITY_NONE = "none", - VISIBILITY_VALUES = [VISIBILITY_ALL, VISIBILITY_FRIENDS, VISIBILITY_NONE], - VISIBILITY_DISPLAY_VALUES = ["everyone", "friends", "no one"], - visibilityControl, - - windowHeight, - windowBorderHeight, - windowTextHeight, - windowLineSpacing, - windowLineHeight, // = windowTextHeight + windowLineSpacing - windowMinimumHeight, - - usersOnline, // Raw users data - linesOfUsers = [], // Array of indexes pointing into usersOnline - numUsersToDisplay = 0, - firstUserToDisplay = 0, - - API_URL = "https://metaverse.highfidelity.com/api/v1/users?status=online", - API_FRIENDS_FILTER = "&filter=friends", - HTTP_GET_TIMEOUT = 60000, // ms = 1 minute - usersRequest, - processUsers, - pollUsersTimedOut, - usersTimer = null, - USERS_UPDATE_TIMEOUT = 5000, // ms = 5s - - showMe, - myVisibility, - - MENU_NAME = "View", - MENU_ITEM = "Users Online", - MENU_ITEM_OVERLAYS = "Overlays", - MENU_ITEM_AFTER = MENU_ITEM_OVERLAYS, - - SETTING_USERS_SHOW_ME = "UsersWindow.ShowMe", - SETTING_USERS_VISIBLE_TO = "UsersWindow.VisibleTo", - SETTING_USERS_WINDOW_MINIMIZED = "UsersWindow.Minimized", - SETTING_USERS_WINDOW_OFFSET = "UsersWindow.Offset", - // +ve x, y values are offset from left, top of screen; -ve from right, bottom. - - isLoggedIn = false, - isVisible = true, - isMinimized = false, - isBorderVisible = false, - - viewport, - isMirrorDisplay = false, - isFullscreenMirror = false, - - windowPosition = {}, // Bottom left corner of window pane. - isMovingWindow = false, - movingClickOffset = { x: 0, y: 0 }, - - isUsingScrollbars = false, - isMovingScrollbar = false, - scrollbarBackgroundPosition = {}, - scrollbarBarPosition = {}, - scrollbarBarClickedAt, // 0.0 .. 1.0 - scrollbarValue = 0.0; // 0.0 .. 1.0 - - function isWindowDisabled() { - return !Menu.isOptionChecked(MENU_ITEM) || !Menu.isOptionChecked(MENU_ITEM_OVERLAYS); - } - - function isValueTrue(value) { - // Work around Boolean Settings values being read as string when Interface starts up but as Booleans when re-read after - // Being written if refresh script. - return value === true || value === "true"; - } - - function calculateWindowHeight() { - var AUDIO_METER_HEIGHT = 52, - MIRROR_HEIGHT = 220, - nonUsersHeight, - maxWindowHeight; - - if (isMinimized) { - windowHeight = windowTextHeight + WINDOW_MARGIN + WINDOW_BASE_MARGIN; - windowBorderHeight = windowHeight + WINDOW_BORDER_TOP_MARGIN + WINDOW_BORDER_BOTTOM_MARGIN; - return; - } - - // Reserve space for title, friends button, and option controls - nonUsersHeight = WINDOW_MARGIN + windowLineHeight + - (shouldShowFriendsButton() ? FRIENDS_BUTTON_SPACER + FRIENDS_BUTTON_HEIGHT : 0) + - DISPLAY_SPACER + - windowLineHeight + VISIBILITY_SPACER + - windowLineHeight + WINDOW_BASE_MARGIN; - - // Limit window to height of viewport above window position minus VU meter and mirror if displayed - windowHeight = linesOfUsers.length * windowLineHeight - windowLineSpacing + nonUsersHeight; - maxWindowHeight = windowPosition.y - AUDIO_METER_HEIGHT; - if (isMirrorDisplay && !isFullscreenMirror) { - maxWindowHeight -= MIRROR_HEIGHT; - } - windowHeight = Math.max(Math.min(windowHeight, maxWindowHeight), nonUsersHeight); - windowBorderHeight = windowHeight + WINDOW_BORDER_TOP_MARGIN + WINDOW_BORDER_BOTTOM_MARGIN; - - // Corresponding number of users to actually display - numUsersToDisplay = Math.max(Math.round((windowHeight - nonUsersHeight) / windowLineHeight), 0); - isUsingScrollbars = 0 < numUsersToDisplay && numUsersToDisplay < linesOfUsers.length; - if (isUsingScrollbars) { - firstUserToDisplay = Math.floor(scrollbarValue * (linesOfUsers.length - numUsersToDisplay)); - } else { - firstUserToDisplay = 0; - scrollbarValue = 0.0; - } - } - - function saturateWindowPosition() { - windowPosition.x = Math.max(WINDOW_MARGIN_X, Math.min(viewport.x - WINDOW_WIDTH - WINDOW_MARGIN_X, windowPosition.x)); - windowPosition.y = Math.max(windowMinimumHeight, Math.min(viewport.y, windowPosition.y)); - } - - function updateOverlayPositions() { - // Overlay positions are all relative to windowPosition; windowPosition is the position of the windowPane overlay. - var windowLeft = windowPosition.x, - windowTop = windowPosition.y - windowHeight, - x, - y; - - Overlays.editOverlay(windowBorder, { - x: windowPosition.x - WINDOW_BORDER_LEFT_MARGIN, - y: windowTop - WINDOW_BORDER_TOP_MARGIN - }); - Overlays.editOverlay(windowPane, { - x: windowLeft, - y: windowTop - }); - Overlays.editOverlay(windowHeading, { - x: windowLeft + WINDOW_MARGIN, - y: windowTop + WINDOW_MARGIN - }); - - Overlays.editOverlay(minimizeButton, { - x: windowLeft + WINDOW_WIDTH - WINDOW_MARGIN / 2 - MIN_MAX_BUTTON_WIDTH, - y: windowTop + WINDOW_MARGIN - }); - - scrollbarBackgroundPosition.x = windowLeft + WINDOW_WIDTH - 0.5 * WINDOW_MARGIN - SCROLLBAR_BACKGROUND_WIDTH; - scrollbarBackgroundPosition.y = windowTop + WINDOW_MARGIN + windowTextHeight; - Overlays.editOverlay(scrollbarBackground, { - x: scrollbarBackgroundPosition.x, - y: scrollbarBackgroundPosition.y - }); - scrollbarBarPosition.y = scrollbarBackgroundPosition.y + 1 + - scrollbarValue * (scrollbarBackgroundHeight - scrollbarBarHeight - 2); - Overlays.editOverlay(scrollbarBar, { - x: scrollbarBackgroundPosition.x + 1, - y: scrollbarBarPosition.y - }); - - - x = windowLeft + WINDOW_MARGIN; - y = windowPosition.y - - DISPLAY_SPACER - - windowLineHeight - VISIBILITY_SPACER - - windowLineHeight - WINDOW_BASE_MARGIN; - if (shouldShowFriendsButton()) { - y -= FRIENDS_BUTTON_HEIGHT; - Overlays.editOverlay(friendsButton, { - x: x, - y: y - }); - y += FRIENDS_BUTTON_HEIGHT; - } - - y += DISPLAY_SPACER; - displayControl.updatePosition(x, y); - - y += windowLineHeight + VISIBILITY_SPACER; - visibilityControl.updatePosition(x, y); - } - - function updateUsersDisplay() { - var displayText = "", - user, - userText, - textWidth, - maxTextWidth, - ellipsisWidth, - reducedTextWidth, - i; - - if (!isMinimized) { - maxTextWidth = WINDOW_WIDTH - (isUsingScrollbars ? SCROLLBAR_BACKGROUND_WIDTH : 0) - 2 * WINDOW_MARGIN; - ellipsisWidth = Overlays.textSize(windowPane, "...").width; - reducedTextWidth = maxTextWidth - ellipsisWidth; - - for (i = 0; i < numUsersToDisplay; i += 1) { - user = usersOnline[linesOfUsers[firstUserToDisplay + i]]; - userText = user.text; - textWidth = user.textWidth; - - if (textWidth > maxTextWidth) { - // Trim and append "..." to fit window width - maxTextWidth = maxTextWidth - Overlays.textSize(windowPane, "...").width; - while (textWidth > reducedTextWidth) { - userText = userText.slice(0, -1); - textWidth = Overlays.textSize(windowPane, userText).width; - } - userText += "..."; - } - - displayText += "\n" + userText; - } - - displayText = displayText.slice(1); // Remove leading "\n". - - scrollbarBackgroundHeight = numUsersToDisplay * windowLineHeight - windowLineSpacing / 2; - Overlays.editOverlay(scrollbarBackground, { - height: scrollbarBackgroundHeight, - visible: isLoggedIn && isUsingScrollbars - }); - scrollbarBarHeight = Math.max(numUsersToDisplay / linesOfUsers.length * scrollbarBackgroundHeight, - SCROLLBAR_BAR_MIN_HEIGHT); - Overlays.editOverlay(scrollbarBar, { - height: scrollbarBarHeight, - visible: isLoggedIn && isUsingScrollbars - }); - } - - Overlays.editOverlay(windowBorder, { - height: windowBorderHeight - }); - - Overlays.editOverlay(windowPane, { - height: windowHeight, - text: displayText - }); - - Overlays.editOverlay(windowHeading, { - text: isLoggedIn ? (linesOfUsers.length > 0 ? "Users online" : "No users online") : "Users online - log in to view" - }); - } - - function shouldShowFriendsButton() { - return isVisible && isLoggedIn && !isMinimized; - } - - function updateOverlayVisibility() { - Overlays.editOverlay(windowBorder, { - visible: isVisible && isBorderVisible - }); - Overlays.editOverlay(windowPane, { - visible: isVisible - }); - Overlays.editOverlay(windowHeading, { - visible: isVisible - }); - Overlays.editOverlay(minimizeButton, { - visible: isVisible - }); - Overlays.editOverlay(scrollbarBackground, { - visible: isVisible && isUsingScrollbars && !isMinimized - }); - Overlays.editOverlay(scrollbarBar, { - visible: isVisible && isUsingScrollbars && !isMinimized - }); - Overlays.editOverlay(friendsButton, { - visible: shouldShowFriendsButton() - }); - displayControl.setVisible(isVisible && !isMinimized); - visibilityControl.setVisible(isVisible && !isMinimized); - } - - function checkLoggedIn() { - var wasLoggedIn = isLoggedIn; - - isLoggedIn = Account.isLoggedIn(); - if (isLoggedIn !== wasLoggedIn) { - if (wasLoggedIn) { - setMinimized(true); - calculateWindowHeight(); - updateOverlayPositions(); - updateUsersDisplay(); - } - - updateOverlayVisibility(); - } - } - - function pollUsers() { - var url = API_URL; - - if (showMe === DISPLAY_FRIENDS) { - url += API_FRIENDS_FILTER; - } - - usersRequest = new XMLHttpRequest(); - usersRequest.open("GET", url, true); - usersRequest.timeout = HTTP_GET_TIMEOUT; - usersRequest.ontimeout = pollUsersTimedOut; - usersRequest.onreadystatechange = processUsers; - usersRequest.send(); - } - - processUsers = function () { - var response, - myUsername, - user, - userText, - i; - - if (usersRequest.readyState === usersRequest.DONE) { - if (usersRequest.status === 200) { - response = JSON.parse(usersRequest.responseText); - if (response.status !== "success") { - print("Error: Request for users status returned status = " + response.status); - usersTimer = Script.setTimeout(pollUsers, HTTP_GET_TIMEOUT); // Try again after a longer delay. - return; - } - if (!response.hasOwnProperty("data") || !response.data.hasOwnProperty("users")) { - print("Error: Request for users status returned invalid data"); - usersTimer = Script.setTimeout(pollUsers, HTTP_GET_TIMEOUT); // Try again after a longer delay. - return; - } - - usersOnline = response.data.users; - myUsername = GlobalServices.username; - linesOfUsers = []; - for (i = 0; i < usersOnline.length; i += 1) { - user = usersOnline[i]; - if (user.username !== myUsername && user.online) { - userText = user.username; - if (user.location.root) { - userText += " @ " + user.location.root.name; - } - - usersOnline[i].text = userText; - usersOnline[i].textWidth = Overlays.textSize(windowPane, userText).width; - - linesOfUsers.push(i); - } - } - - checkLoggedIn(); - calculateWindowHeight(); - updateUsersDisplay(); - updateOverlayPositions(); - - } else { - print("Error: Request for users status returned " + usersRequest.status + " " + usersRequest.statusText); - usersTimer = Script.setTimeout(pollUsers, HTTP_GET_TIMEOUT); // Try again after a longer delay. - return; - } - - usersTimer = Script.setTimeout(pollUsers, USERS_UPDATE_TIMEOUT); // Update after finished processing. - } - }; - - pollUsersTimedOut = function () { - print("Error: Request for users status timed out"); - usersTimer = Script.setTimeout(pollUsers, HTTP_GET_TIMEOUT); // Try again after a longer delay. - }; - - function setVisible(visible) { - isVisible = visible; - - if (isVisible) { - if (usersTimer === null) { - pollUsers(); - } - } else { - Script.clearTimeout(usersTimer); - usersTimer = null; - } - - updateOverlayVisibility(); - } - - function setMinimized(minimized) { - isMinimized = minimized; - Overlays.editOverlay(minimizeButton, { - subImage: { - y: isMinimized ? MIN_MAX_BUTTON_SVG_HEIGHT / 2 : 0 - } - }); - updateOverlayVisibility(); - Settings.setValue(SETTING_USERS_WINDOW_MINIMIZED, isMinimized); - } - - function onMenuItemEvent(event) { - if (event === MENU_ITEM) { - setVisible(Menu.isOptionChecked(MENU_ITEM)); - } - } - - function onFindableByChanged(event) { - if (VISIBILITY_VALUES.indexOf(event) !== -1) { - myVisibility = event; - visibilityControl.setValue(event); - Settings.setValue(SETTING_USERS_VISIBLE_TO, myVisibility); - } else { - print("Error: Unrecognized onFindableByChanged value: " + event); - } - } - - function onMousePressEvent(event) { - var clickedOverlay, - numLinesBefore, - overlayX, - overlayY, - minY, - maxY, - lineClicked, - userClicked, - delta; - - if (!isVisible || isWindowDisabled()) { - return; - } - - clickedOverlay = Overlays.getOverlayAtPoint({ - x: event.x, - y: event.y - }); - - if (displayControl.handleClick(clickedOverlay)) { - if (usersTimer !== null) { - Script.clearTimeout(usersTimer); - usersTimer = null; - } - pollUsers(); - showMe = displayControl.getValue(); - Settings.setValue(SETTING_USERS_SHOW_ME, showMe); - return; - } - - if (visibilityControl.handleClick(clickedOverlay)) { - myVisibility = visibilityControl.getValue(); - GlobalServices.findableBy = myVisibility; - Settings.setValue(SETTING_USERS_VISIBLE_TO, myVisibility); - return; - } - - if (clickedOverlay === windowPane) { - - overlayX = event.x - windowPosition.x - WINDOW_MARGIN; - overlayY = event.y - windowPosition.y + windowHeight - WINDOW_MARGIN - windowLineHeight; - - numLinesBefore = Math.round(overlayY / windowLineHeight); - minY = numLinesBefore * windowLineHeight; - maxY = minY + windowTextHeight; - - lineClicked = -1; - if (minY <= overlayY && overlayY <= maxY) { - lineClicked = numLinesBefore; - } - - userClicked = firstUserToDisplay + lineClicked; - - if (0 <= userClicked && userClicked < linesOfUsers.length && 0 <= overlayX && - overlayX <= usersOnline[linesOfUsers[userClicked]].textWidth) { - //print("Go to " + usersOnline[linesOfUsers[userClicked]].username); - location.goToUser(usersOnline[linesOfUsers[userClicked]].username); - } - - return; - } - - if (clickedOverlay === minimizeButton) { - setMinimized(!isMinimized); - calculateWindowHeight(); - updateOverlayPositions(); - updateUsersDisplay(); - return; - } - - if (clickedOverlay === scrollbarBar) { - scrollbarBarClickedAt = (event.y - scrollbarBarPosition.y) / scrollbarBarHeight; - Overlays.editOverlay(scrollbarBar, { - backgroundAlpha: SCROLLBAR_BAR_SELECTED_ALPHA - }); - isMovingScrollbar = true; - return; - } - - if (clickedOverlay === scrollbarBackground) { - delta = scrollbarBarHeight / (scrollbarBackgroundHeight - scrollbarBarHeight); - - if (event.y < scrollbarBarPosition.y) { - scrollbarValue = Math.max(scrollbarValue - delta, 0.0); - } else { - scrollbarValue = Math.min(scrollbarValue + delta, 1.0); - } - - firstUserToDisplay = Math.floor(scrollbarValue * (linesOfUsers.length - numUsersToDisplay)); - updateOverlayPositions(); - updateUsersDisplay(); - return; - } - - if (clickedOverlay === friendsButton) { - if (!friendsWindow) { - friendsWindow = new OverlayWebWindow({ - title: FRIENDS_WINDOW_TITLE, - width: FRIENDS_WINDOW_WIDTH, - height: FRIENDS_WINDOW_HEIGHT, - visible: false - }); - } - friendsWindow.setURL(FRIENDS_WINDOW_URL); - friendsWindow.setVisible(true); - friendsWindow.raise(); - return; - } - - if (clickedOverlay === windowBorder) { - movingClickOffset = { - x: event.x - windowPosition.x, - y: event.y - windowPosition.y - }; - - isMovingWindow = true; - } - } - - function onMouseMoveEvent(event) { - var isVisible; - - if (!isLoggedIn || isWindowDisabled()) { - return; - } - - if (isMovingScrollbar) { - if (scrollbarBackgroundPosition.x - WINDOW_MARGIN <= event.x && - event.x <= scrollbarBackgroundPosition.x + SCROLLBAR_BACKGROUND_WIDTH + WINDOW_MARGIN && - scrollbarBackgroundPosition.y - WINDOW_MARGIN <= event.y && - event.y <= scrollbarBackgroundPosition.y + scrollbarBackgroundHeight + WINDOW_MARGIN) { - scrollbarValue = (event.y - scrollbarBarClickedAt * scrollbarBarHeight - scrollbarBackgroundPosition.y) / - (scrollbarBackgroundHeight - scrollbarBarHeight - 2); - scrollbarValue = Math.min(Math.max(scrollbarValue, 0.0), 1.0); - firstUserToDisplay = Math.floor(scrollbarValue * (linesOfUsers.length - numUsersToDisplay)); - updateOverlayPositions(); - updateUsersDisplay(); - } else { - Overlays.editOverlay(scrollbarBar, { - backgroundAlpha: SCROLLBAR_BAR_ALPHA - }); - isMovingScrollbar = false; - } - } - - if (isMovingWindow) { - windowPosition = { - x: event.x - movingClickOffset.x, - y: event.y - movingClickOffset.y - }; - - saturateWindowPosition(); - calculateWindowHeight(); - updateOverlayPositions(); - updateUsersDisplay(); - - } else { - - isVisible = isBorderVisible; - if (isVisible) { - isVisible = windowPosition.x - WINDOW_BORDER_LEFT_MARGIN <= event.x && - event.x <= windowPosition.x - WINDOW_BORDER_LEFT_MARGIN + WINDOW_BORDER_WIDTH && - windowPosition.y - windowHeight - WINDOW_BORDER_TOP_MARGIN <= event.y && - event.y <= windowPosition.y + WINDOW_BORDER_BOTTOM_MARGIN; - } else { - isVisible = windowPosition.x <= event.x && event.x <= windowPosition.x + WINDOW_WIDTH && - windowPosition.y - windowHeight <= event.y && event.y <= windowPosition.y; - } - if (isVisible !== isBorderVisible) { - isBorderVisible = isVisible; - Overlays.editOverlay(windowBorder, { - visible: isBorderVisible - }); - } - } - } - - function onMouseReleaseEvent() { - var offset = {}; - - if (isWindowDisabled()) { - return; - } - - if (isMovingScrollbar) { - Overlays.editOverlay(scrollbarBar, { - backgroundAlpha: SCROLLBAR_BAR_ALPHA - }); - isMovingScrollbar = false; - } - - if (isMovingWindow) { - // Save offset of bottom of window to nearest edge of the window. - offset.x = (windowPosition.x + WINDOW_WIDTH / 2 < viewport.x / 2) ? - windowPosition.x : windowPosition.x - viewport.x; - offset.y = (windowPosition.y < viewport.y / 2) ? - windowPosition.y : windowPosition.y - viewport.y; - Settings.setValue(SETTING_USERS_WINDOW_OFFSET, JSON.stringify(offset)); - isMovingWindow = false; - } - } - - function onScriptUpdate() { - var oldViewport = viewport, - oldIsMirrorDisplay = isMirrorDisplay, - oldIsFullscreenMirror = isFullscreenMirror, - MIRROR_MENU_ITEM = "Mirror", - FULLSCREEN_MIRROR_MENU_ITEM = "Fullscreen Mirror"; - - if (isWindowDisabled()) { - return; - } - - viewport = Controller.getViewportDimensions(); - isMirrorDisplay = Menu.isOptionChecked(MIRROR_MENU_ITEM); - isFullscreenMirror = Menu.isOptionChecked(FULLSCREEN_MIRROR_MENU_ITEM); - - if (viewport.y !== oldViewport.y || isMirrorDisplay !== oldIsMirrorDisplay || - isFullscreenMirror !== oldIsFullscreenMirror) { - calculateWindowHeight(); - updateUsersDisplay(); - } - - if (viewport.y !== oldViewport.y) { - if (windowPosition.y > oldViewport.y / 2) { - // Maintain position w.r.t. bottom of window. - windowPosition.y = viewport.y - (oldViewport.y - windowPosition.y); - } - } - - if (viewport.x !== oldViewport.x) { - if (windowPosition.x + (WINDOW_WIDTH / 2) > oldViewport.x / 2) { - // Maintain position w.r.t. right of window. - windowPosition.x = viewport.x - (oldViewport.x - windowPosition.x); - } - } - - updateOverlayPositions(); - } - - function setUp() { - var textSizeOverlay, - offsetSetting, - offset = {}, - hmdViewport; - - textSizeOverlay = Overlays.addOverlay("text", { - font: WINDOW_FONT, - visible: false - }); - windowTextHeight = Math.floor(Overlays.textSize(textSizeOverlay, "1").height); - windowLineSpacing = Math.floor(Overlays.textSize(textSizeOverlay, "1\n2").height - 2 * windowTextHeight); - windowLineHeight = windowTextHeight + windowLineSpacing; - windowMinimumHeight = windowTextHeight + WINDOW_MARGIN + WINDOW_BASE_MARGIN; - Overlays.deleteOverlay(textSizeOverlay); - - viewport = Controller.getViewportDimensions(); - - offsetSetting = Settings.getValue(SETTING_USERS_WINDOW_OFFSET); - if (offsetSetting !== "") { - offset = JSON.parse(Settings.getValue(SETTING_USERS_WINDOW_OFFSET)); - } - if (offset.hasOwnProperty("x") && offset.hasOwnProperty("y")) { - windowPosition.x = offset.x < 0 ? viewport.x + offset.x : offset.x; - windowPosition.y = offset.y <= 0 ? viewport.y + offset.y : offset.y; - } else { - hmdViewport = Controller.getRecommendedOverlayRect(); - windowPosition = { - x: (viewport.x - hmdViewport.width) / 2, // HMD viewport is narrower than screen. - y: hmdViewport.height // HMD viewport starts at top of screen but only extends down so far. - }; - } - - saturateWindowPosition(); - calculateWindowHeight(); - - windowBorder = Overlays.addOverlay("rectangle", { - x: 0, - y: viewport.y, // Start up off-screen - width: WINDOW_BORDER_WIDTH, - height: windowBorderHeight, - radius: WINDOW_BORDER_RADIUS, - color: WINDOW_BORDER_COLOR, - alpha: WINDOW_BORDER_ALPHA, - visible: false - }); - - windowPane = Overlays.addOverlay("text", { - x: 0, - y: viewport.y, - width: WINDOW_WIDTH, - height: windowHeight, - topMargin: WINDOW_MARGIN + windowLineHeight, - leftMargin: WINDOW_MARGIN, - color: WINDOW_FOREGROUND_COLOR, - alpha: WINDOW_FOREGROUND_ALPHA, - backgroundColor: WINDOW_BACKGROUND_COLOR, - backgroundAlpha: WINDOW_BACKGROUND_ALPHA, - text: "", - font: WINDOW_FONT, - visible: false - }); - - windowHeading = Overlays.addOverlay("text", { - x: 0, - y: viewport.y, - width: WINDOW_WIDTH - 2 * WINDOW_MARGIN, - height: windowTextHeight, - topMargin: 0, - leftMargin: 0, - color: WINDOW_HEADING_COLOR, - alpha: WINDOW_HEADING_ALPHA, - backgroundAlpha: 0.0, - text: "Users online", - font: WINDOW_FONT, - visible: false - }); - - minimizeButton = Overlays.addOverlay("image", { - x: 0, - y: viewport.y, - width: MIN_MAX_BUTTON_WIDTH, - height: MIN_MAX_BUTTON_HEIGHT, - imageURL: MIN_MAX_BUTTON_SVG, - subImage: { - x: 0, - y: 0, - width: MIN_MAX_BUTTON_SVG_WIDTH, - height: MIN_MAX_BUTTON_SVG_HEIGHT / 2 - }, - color: MIN_MAX_BUTTON_COLOR, - alpha: MIN_MAX_BUTTON_ALPHA, - visible: false - }); - - scrollbarBackgroundPosition = { - x: 0, - y: viewport.y - }; - scrollbarBackground = Overlays.addOverlay("text", { - x: 0, - y: scrollbarBackgroundPosition.y, - width: SCROLLBAR_BACKGROUND_WIDTH, - height: windowTextHeight, - backgroundColor: SCROLLBAR_BACKGROUND_COLOR, - backgroundAlpha: SCROLLBAR_BACKGROUND_ALPHA, - text: "", - visible: false - }); - - scrollbarBarPosition = { - x: 0, - y: viewport.y - }; - scrollbarBar = Overlays.addOverlay("text", { - x: 0, - y: scrollbarBarPosition.y, - width: SCROLLBAR_BACKGROUND_WIDTH - 2, - height: windowTextHeight, - backgroundColor: SCROLLBAR_BAR_COLOR, - backgroundAlpha: SCROLLBAR_BAR_ALPHA, - text: "", - visible: false - }); - - friendsButton = Overlays.addOverlay("image", { - x: 0, - y: viewport.y, - width: FRIENDS_BUTTON_WIDTH, - height: FRIENDS_BUTTON_HEIGHT, - imageURL: FRIENDS_BUTTON_SVG, - subImage: { - x: 0, - y: 0, - width: FRIENDS_BUTTON_SVG_WIDTH, - height: FRIENDS_BUTTON_SVG_HEIGHT - }, - color: FRIENDS_BUTTON_COLOR, - alpha: FRIENDS_BUTTON_ALPHA, - visible: false - }); - - showMe = Settings.getValue(SETTING_USERS_SHOW_ME, ""); - if (DISPLAY_VALUES.indexOf(showMe) === -1) { - showMe = DISPLAY_EVERYONE; - } - - displayControl = new PopUpMenu({ - prompt: DISPLAY_PROMPT, - value: showMe, - values: DISPLAY_VALUES, - displayValues: DISPLAY_DISPLAY_VALUES, - x: 0, - y: viewport.y, - width: WINDOW_WIDTH - 1.5 * WINDOW_MARGIN, - promptWidth: DISPLAY_PROMPT_WIDTH, - lineHeight: windowLineHeight, - textHeight: windowTextHeight, - font: WINDOW_FONT, - promptColor: WINDOW_HEADING_COLOR, - promptAlpha: WINDOW_HEADING_ALPHA, - promptBackgroundColor: WINDOW_BACKGROUND_COLOR, - promptBackgroundAlpha: 0.0, - optionColor: WINDOW_FOREGROUND_COLOR, - optionAlpha: WINDOW_FOREGROUND_ALPHA, - optionBackgroundColor: OPTION_BACKGROUND_COLOR, - optionBackgroundAlpha: OPTION_BACKGROUND_ALPHA, - popupBackgroundColor: DISPLAY_OPTIONS_BACKGROUND_COLOR, - popupBackgroundAlpha: DISPLAY_OPTIONS_BACKGROUND_ALPHA, - buttonColor: MIN_MAX_BUTTON_COLOR, - buttonAlpha: MIN_MAX_BUTTON_ALPHA, - visible: false - }); - - myVisibility = Settings.getValue(SETTING_USERS_VISIBLE_TO, ""); - if (VISIBILITY_VALUES.indexOf(myVisibility) === -1) { - myVisibility = VISIBILITY_FRIENDS; - } - GlobalServices.findableBy = myVisibility; - - visibilityControl = new PopUpMenu({ - prompt: VISIBILITY_PROMPT, - value: myVisibility, - values: VISIBILITY_VALUES, - displayValues: VISIBILITY_DISPLAY_VALUES, - x: 0, - y: viewport.y, - width: WINDOW_WIDTH - 1.5 * WINDOW_MARGIN, - promptWidth: VISIBILITY_PROMPT_WIDTH, - lineHeight: windowLineHeight, - textHeight: windowTextHeight, - font: WINDOW_FONT, - promptColor: WINDOW_HEADING_COLOR, - promptAlpha: WINDOW_HEADING_ALPHA, - promptBackgroundColor: WINDOW_BACKGROUND_COLOR, - promptBackgroundAlpha: 0.0, - optionColor: WINDOW_FOREGROUND_COLOR, - optionAlpha: WINDOW_FOREGROUND_ALPHA, - optionBackgroundColor: OPTION_BACKGROUND_COLOR, - optionBackgroundAlpha: OPTION_BACKGROUND_ALPHA, - popupBackgroundColor: DISPLAY_OPTIONS_BACKGROUND_COLOR, - popupBackgroundAlpha: DISPLAY_OPTIONS_BACKGROUND_ALPHA, - buttonColor: MIN_MAX_BUTTON_COLOR, - buttonAlpha: MIN_MAX_BUTTON_ALPHA, - visible: false - }); - - Controller.mousePressEvent.connect(onMousePressEvent); - Controller.mouseMoveEvent.connect(onMouseMoveEvent); - Controller.mouseReleaseEvent.connect(onMouseReleaseEvent); - - Menu.addMenuItem({ - menuName: MENU_NAME, - menuItemName: MENU_ITEM, - afterItem: MENU_ITEM_AFTER, - isCheckable: true, - isChecked: isVisible - }); - Menu.menuItemEvent.connect(onMenuItemEvent); - - GlobalServices.findableByChanged.connect(onFindableByChanged); - - Script.update.connect(onScriptUpdate); - - pollUsers(); - - // Set minimized at end - setup code does not handle `minimized == false` correctly - setMinimized(isValueTrue(Settings.getValue(SETTING_USERS_WINDOW_MINIMIZED, true))); - } - - function tearDown() { - Menu.removeMenuItem(MENU_NAME, MENU_ITEM); - - Script.clearTimeout(usersTimer); - Overlays.deleteOverlay(windowBorder); - Overlays.deleteOverlay(windowPane); - Overlays.deleteOverlay(windowHeading); - Overlays.deleteOverlay(minimizeButton); - Overlays.deleteOverlay(scrollbarBackground); - Overlays.deleteOverlay(scrollbarBar); - Overlays.deleteOverlay(friendsButton); - displayControl.tearDown(); - visibilityControl.tearDown(); - } - - setUp(); - Script.scriptEnding.connect(tearDown); -}()); - -function cleanup () { - //remove tablet button - button.clicked.disconnect(onClicked); - if (tablet) { - tablet.removeButton(button); - } - if (toolBar) { - toolBar.removeButton(buttonName); - } -} -Script.scriptEnding.connect(cleanup); - -}()); // END LOCAL_SCOPE