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/TabletFileDialog.qml b/interface/resources/qml/dialogs/TabletFileDialog.qml new file mode 100644 index 0000000000..0aef9179e8 --- /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: { + console.log("Helper " + helper + " drives " + drives); + + fileDialogItem.keyboardEnabled = HMD.active; + + // HACK: The following lines force the model to initialize properly such that the go-up button + // 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 + anchors { + top: parent.top + topMargin: hifi.dimensions.contentMargin.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 + 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/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/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/TabletRoot.qml b/interface/resources/qml/hifi/tablet/TabletRoot.qml index 1fb31e5619..8eaddfb601 100644 --- a/interface/resources/qml/hifi/tablet/TabletRoot.qml +++ b/interface/resources/qml/hifi/tablet/TabletRoot.qml @@ -1,6 +1,7 @@ import QtQuick 2.0 import Hifi 1.0 - +import QtQuick.Controls 1.4 +import "../../dialogs" Item { id: tabletRoot objectName: "tabletRoot" @@ -8,19 +9,48 @@ 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; + } + + function customInputDialog(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 +98,7 @@ Item { objectName: "loader" asynchronous: false + width: parent.width height: parent.height @@ -89,6 +120,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/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/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 d48fe19a99..8bb2092f94 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -843,6 +843,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. @@ -1623,9 +1626,13 @@ void Application::toggleTabletUI() const { return; } lastTabletUIToggle = now; - - auto HMD = DependencyManager::get(); - HMD->toggleShouldShowTablet(); + 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(); + } } void Application::checkChangeCursor() { @@ -1957,6 +1964,7 @@ 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()); @@ -5779,8 +5787,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(); @@ -6897,6 +6920,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/scripting/HMDScriptingInterface.cpp b/interface/src/scripting/HMDScriptingInterface.cpp index 2bca793d80..37f10ed415 100644 --- a/interface/src/scripting/HMDScriptingInterface.cpp +++ b/interface/src/scripting/HMDScriptingInterface.cpp @@ -131,7 +131,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/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 bfc37ccf60..8d72f877b6 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -38,8 +38,10 @@ #include "scripting/AccountScriptingInterface.h" #include "scripting/HMDScriptingInterface.h" #include +#include #include "FileDialogHelper.h" + static const float DPI = 30.47f; static const float INCHES_TO_METERS = 1.0f / 39.3701f; static const float METERS_TO_INCHES = 39.3701f; @@ -170,6 +172,8 @@ void Web3DOverlay::loadSourceURL() { _webSurface->getRootContext()->setContextProperty("Account", AccountScriptingInterface::getInstance()); _webSurface->getRootContext()->setContextProperty("HMD", DependencyManager::get().data()); _webSurface->getRootContext()->setContextProperty("fileDialogHelper", new FileDialogHelper()); + _webSurface->getRootContext()->setContextProperty("ScriptDiscoveryService", 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 @@ -344,9 +348,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); diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 55a7221f5d..d10aac1521 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..2d940d6755 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" @@ -911,20 +912,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..f9bb1b59ad 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; @@ -331,6 +343,48 @@ 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::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"; } } @@ -560,7 +614,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..f17d441023 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; 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/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index d982a032cc..590a1587ea 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 @@ -739,6 +739,7 @@ function MyController(hand) { this.grabPointIntersectsEntity = false; this.stylus = null; this.homeButtonTouched = false; + this.editTriggered = false; // Until there is some reliable way to keep track of a "stack" of parentIDs, we'll have problems // when more than one avatar does parenting grabs on things. This script tries to work @@ -1048,7 +1049,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 : @@ -1217,6 +1218,10 @@ function MyController(hand) { this.checkForUnexpectedChildren(); + if (this.editTriggered) { + this.editTriggered = false; + } + if (this.triggerSmoothedReleased() && this.secondaryReleased()) { this.waitForTriggerRelease = false; } @@ -1683,23 +1688,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; + } } } } @@ -1714,6 +1717,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; diff --git a/scripts/system/edit.js b/scripts/system/edit.js index ad3af3a659..404cc26fcb 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/"; @@ -58,18 +59,13 @@ selectionManager.addEventListener(function () { 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, @@ -84,7 +80,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"; @@ -156,13 +151,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/"); @@ -184,8 +179,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(); @@ -205,24 +204,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() { @@ -239,101 +277,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 () { @@ -455,10 +432,12 @@ var toolBar = (function () { entityListTool.clearEntityList(); }; - that.toggle = function () { that.setActive(!isActive); activeButton.editProperties({isActive: isActive}); + if (!isActive) { + tablet.gotoHomeScreen(); + } }; that.setActive = function (active) { @@ -488,6 +467,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); @@ -498,13 +479,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)); }; @@ -641,7 +615,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; @@ -771,6 +744,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]); @@ -1213,11 +1192,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; } @@ -1231,11 +1210,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; @@ -1436,11 +1415,11 @@ var ServerScriptStatusMonitor = function(entityID, statusCallback) { Entities.getServerScriptStatus(entityID, onStatusReceived); } }, 1000); - }; + } }; self.stop = function() { self.active = false; - } + }; Entities.getServerScriptStatus(entityID, onStatusReceived); }; @@ -1448,11 +1427,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; @@ -1471,7 +1448,7 @@ var PropertiesTool = function (opts) { function updateScriptStatus(info) { info.type = "server_script_status"; webView.emitScriptEvent(JSON.stringify(info)); - }; + } function resetScriptStatus() { updateScriptStatus({ @@ -1530,7 +1507,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; @@ -1548,15 +1525,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 @@ -1862,6 +1839,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 === "selectionUpdate") { @@ -1873,22 +1865,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/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 b11127b26c..59c420b547 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/tablet-users.js b/scripts/system/tablet-users.js index 8e89ac74b7..e993ad435e 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); }