diff --git a/interface/resources/qml/AssetServer.qml b/interface/resources/qml/hifi/AssetServer.qml similarity index 80% rename from interface/resources/qml/AssetServer.qml rename to interface/resources/qml/hifi/AssetServer.qml index 649ea49153..e54d4048ec 100644 --- a/interface/resources/qml/AssetServer.qml +++ b/interface/resources/qml/hifi/AssetServer.qml @@ -14,10 +14,10 @@ import QtQuick.Controls.Styles 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" -import "dialogs" +import "../styles-uit" +import "../controls-uit" as HifiControls +import "../windows" +import "../dialogs" ScrollingWindow { id: root @@ -58,6 +58,14 @@ ScrollingWindow { assetMappingsModel.autoRefreshEnabled = false; } + function letterbox(headerGlyph, headerText, message) { + letterboxMessage.headerGlyph = headerGlyph; + letterboxMessage.headerText = headerText; + letterboxMessage.text = message; + letterboxMessage.visible = true; + letterboxMessage.popupRadius = 0; + } + function doDeleteFile(path) { console.log("Deleting " + path); @@ -154,10 +162,7 @@ ScrollingWindow { } function handleGetMappingsError(errorString) { - errorMessageBox( - "There was a problem retreiving the list of assets from your Asset Server.\n" - + errorString - ); + errorMessageBox("There was a problem retreiving the list of assets from your Asset Server.\n" + errorString); } function addToWorld() { @@ -457,10 +462,16 @@ ScrollingWindow { text: message }); } - + Item { width: pane.contentWidth height: pane.height + + // The letterbox used for popup messages + LetterboxMessage { + id: letterboxMessage; + z: 999; // Force the popup on top of everything else + } HifiControls.ContentSection { id: assetDirectory @@ -476,7 +487,7 @@ ScrollingWindow { HifiControls.Button { text: "Add To World" - color: hifi.buttons.black + color: hifi.buttons.blue colorScheme: root.colorScheme width: 120 @@ -513,6 +524,7 @@ ScrollingWindow { id: treeView anchors.top: assetDirectory.bottom anchors.bottom: infoRow.top + anchors.bottomMargin: 2 * hifi.dimensions.contentSpacing.y anchors.margins: hifi.dimensions.contentMargin.x + 2 // Extra for border anchors.left: parent.left anchors.right: parent.right @@ -584,8 +596,24 @@ ScrollingWindow { ? (styleData.selected ? hifi.colors.black : hifi.colors.baseGrayHighlight) : (styleData.selected ? hifi.colors.black : hifi.colors.lightGrayText) - elide: Text.ElideRight horizontalAlignment: styleData.column === 1 ? TextInput.AlignHCenter : TextInput.AlignLeft + + elide: Text.ElideMiddle + + MouseArea { + id: mouseArea + anchors.fill: parent + + acceptedButtons: Qt.NoButton + hoverEnabled: true + + onEntered: { + if (parent.truncated) { + treeLabelToolTip.show(parent); + } + } + onExited: treeLabelToolTip.hide(); + } } } Component { @@ -668,6 +696,42 @@ ScrollingWindow { } } + Rectangle { + id: treeLabelToolTip + visible: false + z: 100 // Render on top + + width: toolTipText.width + 2 * hifi.dimensions.textPadding + height: hifi.dimensions.tableRowHeight + color: colorScheme == hifi.colorSchemes.light ? hifi.colors.tableRowLightOdd : hifi.colors.tableRowDarkOdd + border.color: colorScheme == hifi.colorSchemes.light ? hifi.colors.black : hifi.colors.lightGrayText + + FiraSansSemiBold { + id: toolTipText + anchors.centerIn: parent + + size: hifi.fontSizes.tableText + color: colorScheme == hifi.colorSchemes.light ? hifi.colors.black : hifi.colors.lightGrayText + } + + Timer { + id: showTimer + interval: 1000 + onTriggered: { treeLabelToolTip.visible = true; } + } + function show(item) { + var coord = item.mapToItem(parent, item.x, item.y); + + toolTipText.text = item.text; + treeLabelToolTip.x = coord.x - hifi.dimensions.textPadding; + treeLabelToolTip.y = coord.y; + showTimer.start(); + } + function hide() { + showTimer.stop(); + treeLabelToolTip.visible = false; + } + } MouseArea { propagateComposedEvents: true @@ -712,31 +776,43 @@ ScrollingWindow { } } - Row { + Item { id: infoRow anchors.left: treeView.left anchors.right: treeView.right anchors.bottom: uploadSection.top - anchors.bottomMargin: hifi.dimensions.contentSpacing.y - spacing: hifi.dimensions.contentSpacing.x + anchors.topMargin: 2 * hifi.dimensions.contentSpacing.y + anchors.bottomMargin: 2 * hifi.dimensions.contentSpacing.y RalewayRegular { + id: treeInfo + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + + function makeText() { + var pendingBakes = assetMappingsModel.bakesPendingCount; + if (selectedItems > 1 || pendingBakes === 0) { + return selectedItems + " items selected"; + } else { + return pendingBakes + " bakes pending" + } + } + size: hifi.fontSizes.sectionName font.capitalization: Font.AllUppercase - text: selectedItems + " items selected" + text: makeText() color: hifi.colors.lightGrayText } HifiControls.CheckBox { - function isChecked() { - var status = assetProxyModel.data(treeView.selection.currentIndex, 0x105); - var bakingDisabled = (status === "Not Baked" || status === "--"); - return selectedItems === 1 && !bakingDisabled; - } + id: bakingCheckbox + anchors.left: treeInfo.right + anchors.leftMargin: 2 * hifi.dimensions.contentSpacing.x + anchors.verticalCenter: parent.verticalCenter - text: "Use baked (optimized) versions" + text: " Use baked version" colorScheme: root.colorScheme - enabled: selectedItems === 1 && assetProxyModel.data(treeView.selection.currentIndex, 0x105) !== "--" + enabled: isEnabled() checked: isChecked() onClicked: { var mappings = []; @@ -752,7 +828,72 @@ ScrollingWindow { checked = Qt.binding(isChecked); } + + function isEnabled() { + if (!treeView.selection.hasSelection) { + return false; + } + + var status = assetProxyModel.data(treeView.selection.currentIndex, 0x105); + if (status === "--") { + return false; + } + var bakingEnabled = status !== "Not Baked"; + + for (var i in treeView.selection.selectedIndexes) { + var thisStatus = assetProxyModel.data(treeView.selection.selectedIndexes[i], 0x105); + if (thisStatus === "--") { + return false; + } + var thisBakingEnalbed = (thisStatus !== "Not Baked"); + + if (bakingEnabled !== thisBakingEnalbed) { + return false; + } + } + + return true; + } + function isChecked() { + if (!treeView.selection.hasSelection) { + return false; + } + + var status = assetProxyModel.data(treeView.selection.currentIndex, 0x105); + return isEnabled() && status !== "Not Baked"; + } } + + Item { + anchors.left: bakingCheckbox.right + anchors.leftMargin: hifi.dimensions.contentSpacing.x + anchors.verticalCenter: parent.verticalCenter + width: infoGlyph.size; + height: infoGlyph.size; + + HiFiGlyphs { + id: infoGlyph; + anchors.fill: parent; + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + text: hifi.glyphs.question; + size: 35; + color: hifi.colors.lightGrayText; + } + MouseArea { + anchors.fill: parent; + hoverEnabled: true; + onEntered: infoGlyph.color = hifi.colors.blueHighlight; + onExited: infoGlyph.color = hifi.colors.lightGrayText; + onClicked: letterbox(hifi.glyphs.question, + "What is baking?", + "Baking is a process we use to compress geometric meshes and textures.
" + + "We do this for efficient storage and transmission of models.
" + + "In some cases, we have been able to achieve 60% compression of original models.

" + + "We highly recommend you leave baking on to enable faster transmission decode of models" + + "in interface resulting in better experience for users visiting your domain."); + } + } } HifiControls.ContentSection { @@ -790,7 +931,7 @@ ScrollingWindow { id: image width: 24 height: 24 - source: "../images/Loading-Outer-Ring.png" + source: "../../images/Loading-Outer-Ring.png" RotationAnimation on rotation { loops: Animation.Infinite from: 0 @@ -801,7 +942,7 @@ ScrollingWindow { Image { width: 24 height: 24 - source: "../images/Loading-Inner-H.png" + source: "../../images/Loading-Inner-H.png" } HifiControls.Label { id: uploadProgressLabel diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index e1c3af1939..d538472450 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -6398,7 +6398,7 @@ void Application::showAssetServerWidget(QString filePath) { if (!DependencyManager::get()->getThisNodeCanWriteAssets()) { return; } - static const QUrl url { "AssetServer.qml" }; + static const QUrl url { "hifi/AssetServer.qml" }; auto startUpload = [=](QQmlContext* context, QObject* newObject){ if (!filePath.isEmpty()) { diff --git a/interface/src/scripting/AssetMappingsScriptingInterface.cpp b/interface/src/scripting/AssetMappingsScriptingInterface.cpp index 5031016c3f..6c6f6dc244 100644 --- a/interface/src/scripting/AssetMappingsScriptingInterface.cpp +++ b/interface/src/scripting/AssetMappingsScriptingInterface.cpp @@ -239,6 +239,7 @@ void AssetMappingModel::refresh() { connect(request, &GetAllMappingsRequest::finished, this, [this](GetAllMappingsRequest* request) mutable { if (request->getError() == MappingRequest::NoError) { + int bakesPendingCount = 0; auto mappings = request->getMappings(); auto existingPaths = _pathToItemMap.keys(); for (auto& mapping : mappings) { @@ -287,6 +288,9 @@ void AssetMappingModel::refresh() { auto statusString = isFolder ? "--" : bakingStatusToString(mapping.second.status); lastItem->setData(statusString, Qt::UserRole + 5); lastItem->setData(mapping.second.bakingErrors, Qt::UserRole + 6); + if (mapping.second.status == Pending) { + ++bakesPendingCount; + } } Q_ASSERT(fullPath == path); @@ -334,6 +338,11 @@ void AssetMappingModel::refresh() { item = nextItem; } } + + if (bakesPendingCount != _bakesPendingCount) { + _bakesPendingCount = bakesPendingCount; + emit bakesPendingCountChanged(_bakesPendingCount); + } } else { emit errorGettingMappings(request->getErrorString()); } diff --git a/interface/src/scripting/AssetMappingsScriptingInterface.h b/interface/src/scripting/AssetMappingsScriptingInterface.h index 04ab488838..49d92ec070 100644 --- a/interface/src/scripting/AssetMappingsScriptingInterface.h +++ b/interface/src/scripting/AssetMappingsScriptingInterface.h @@ -26,6 +26,7 @@ class AssetMappingModel : public QStandardItemModel { Q_OBJECT Q_PROPERTY(bool autoRefreshEnabled READ isAutoRefreshEnabled WRITE setAutoRefreshEnabled) + Q_PROPERTY(int bakesPendingCount READ getBakesPendingCount NOTIFY bakesPendingCountChanged) public: AssetMappingModel(); @@ -38,10 +39,13 @@ public: bool isKnownMapping(QString path) const { return _pathToItemMap.contains(path); } bool isKnownFolder(QString path) const; + int getBakesPendingCount() const { return _bakesPendingCount; } + public slots: void clear(); signals: + void bakesPendingCountChanged(int newCount); void errorGettingMappings(QString errorString); void updated(); @@ -50,6 +54,7 @@ private: QHash _pathToItemMap; QTimer _autoRefreshTimer; + int _bakesPendingCount{ 0 }; }; Q_DECLARE_METATYPE(AssetMappingModel*)