diff --git a/interface/resources/qml/hifi/simplifiedUI/avatarApp/AvatarApp.qml b/interface/resources/qml/hifi/simplifiedUI/avatarApp/AvatarApp.qml index d52dd3f3d7..7dbadc59f4 100644 --- a/interface/resources/qml/hifi/simplifiedUI/avatarApp/AvatarApp.qml +++ b/interface/resources/qml/hifi/simplifiedUI/avatarApp/AvatarApp.qml @@ -9,6 +9,7 @@ // import QtQuick 2.10 +import QtQuick.Layouts 1.3 import "../simplifiedConstants" as SimplifiedConstants import "../simplifiedControls" as SimplifiedControls import "./components" as AvatarAppComponents @@ -79,7 +80,11 @@ Rectangle { errorText.text = "There was a problem while retrieving your inventory. " + "Please try closing and re-opening the Avatar app.\n\nInventory status: " + result.status + "\nMessage: " + result.message; } else if (result.data && result.data.assets && result.data.assets.length === 0 && avatarAppInventoryModel.count === 0) { - errorText.text = "You have not created any avatars yet! Create an avatar with the Avatar Creator, then close and re-open the Avatar App." + emptyInventoryContainer.visible = true; + } + + if (Settings.getValue("simplifiedUI/debugFTUE", 0) === 4) { + emptyInventoryContainer.visible = true; } avatarAppInventoryModel.handlePage(result.status !== "success" && result.message, result); @@ -140,8 +145,95 @@ Rectangle { anchors.rightMargin: 24 } + + Item { + id: emptyInventoryContainer + visible: false + anchors.top: displayNameHeader.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + + Flickable { + id: emptyInventoryFlickable + anchors.fill: parent + contentWidth: parent.width + contentHeight: emptyInventoryLayout.height + clip: true + + ColumnLayout { + id: emptyInventoryLayout + anchors.top: parent.top + anchors.left: parent.left + anchors.leftMargin: 26 + anchors.right: parent.right + anchors.rightMargin: 26 + spacing: 0 + + HifiStylesUit.GraphikSemiBold { + text: "Stand out from the crowd!" + Layout.preferredWidth: parent.width + Layout.preferredHeight: paintedHeight + Layout.topMargin: 16 + size: 28 + color: simplifiedUI.colors.text.white + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.Wrap + } + + HifiStylesUit.GraphikRegular { + text: "Create your custom avatar." + Layout.preferredWidth: parent.width + Layout.preferredHeight: paintedHeight + Layout.topMargin: 2 + size: 18 + wrapMode: Text.Wrap + color: simplifiedUI.colors.text.white + horizontalAlignment: Text.AlignHCenter + } + + Image { + id: avatarImage; + source: "images/avatarProfilePic.png" + Layout.preferredWidth: parent.width + Layout.preferredHeight: 450 + Layout.alignment: Qt.AlignHCenter + mipmap: true + fillMode: Image.PreserveAspectFit + } + + Image { + source: "images/qrCode.jpg" + Layout.preferredWidth: 190 + Layout.preferredHeight: 190 + Layout.alignment: Qt.AlignHCenter + Layout.topMargin: -160 + mipmap: true + fillMode: Image.PreserveAspectFit + } + + HifiStylesUit.GraphikSemiBold { + text: "Scan for Mobile App" + Layout.preferredWidth: parent.width + Layout.preferredHeight: paintedHeight + Layout.topMargin: 12 + size: 28 + color: simplifiedUI.colors.text.white + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.Wrap + } + } + } + + SimplifiedControls.VerticalScrollBar { + parent: emptyInventoryFlickable + } + } + + Item { id: avatarInfoTextContainer + visible: !emptyInventoryContainer.visible width: parent.implicitWidth height: childrenRect.height anchors.top: displayNameHeader.bottom @@ -164,7 +256,7 @@ Rectangle { id: yourAvatarsSubtitle text: "These are the avatars that you've created and uploaded via the Avatar Creator." width: parent.width - wrapMode: Text.WordWrap + wrapMode: Text.Wrap anchors.top: yourAvatarsTitle.bottom anchors.topMargin: 6 verticalAlignment: TextInput.AlignVCenter @@ -208,9 +300,10 @@ Rectangle { anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom + visible: !emptyInventoryContainer.visible AnimatedImage { - visible: !inventoryContentsList.visible && !errorText.visible + visible: !(inventoryContentsList.visible || errorText.visible) anchors.centerIn: parent width: 72 height: width @@ -271,6 +364,8 @@ Rectangle { return; } } + + root.avatarPreviewUrl = "../../images/defaultAvatar.svg"; } function fromScript(message) { diff --git a/interface/resources/qml/hifi/simplifiedUI/avatarApp/images/avatarProfilePic.png b/interface/resources/qml/hifi/simplifiedUI/avatarApp/images/avatarProfilePic.png new file mode 100644 index 0000000000..b59ebb3085 Binary files /dev/null and b/interface/resources/qml/hifi/simplifiedUI/avatarApp/images/avatarProfilePic.png differ diff --git a/interface/resources/qml/hifi/simplifiedUI/avatarApp/images/hero.png b/interface/resources/qml/hifi/simplifiedUI/avatarApp/images/hero.png new file mode 100644 index 0000000000..15c358e024 Binary files /dev/null and b/interface/resources/qml/hifi/simplifiedUI/avatarApp/images/hero.png differ diff --git a/interface/resources/qml/hifi/simplifiedUI/avatarApp/images/qrCode.jpg b/interface/resources/qml/hifi/simplifiedUI/avatarApp/images/qrCode.jpg new file mode 100644 index 0000000000..0674781c45 Binary files /dev/null and b/interface/resources/qml/hifi/simplifiedUI/avatarApp/images/qrCode.jpg differ diff --git a/interface/resources/qml/hifi/simplifiedUI/avatarApp/images/simplifiedAvatar_Blue.png b/interface/resources/qml/hifi/simplifiedUI/avatarApp/images/simplifiedAvatar_Blue.png new file mode 100644 index 0000000000..210d37acb6 Binary files /dev/null and b/interface/resources/qml/hifi/simplifiedUI/avatarApp/images/simplifiedAvatar_Blue.png differ diff --git a/interface/resources/qml/hifi/simplifiedUI/avatarApp/images/simplifiedAvatar_Cyan.png b/interface/resources/qml/hifi/simplifiedUI/avatarApp/images/simplifiedAvatar_Cyan.png new file mode 100644 index 0000000000..726e1c8a69 Binary files /dev/null and b/interface/resources/qml/hifi/simplifiedUI/avatarApp/images/simplifiedAvatar_Cyan.png differ diff --git a/interface/resources/qml/hifi/simplifiedUI/avatarApp/images/simplifiedAvatar_Green.png b/interface/resources/qml/hifi/simplifiedUI/avatarApp/images/simplifiedAvatar_Green.png new file mode 100644 index 0000000000..6db55816dc Binary files /dev/null and b/interface/resources/qml/hifi/simplifiedUI/avatarApp/images/simplifiedAvatar_Green.png differ diff --git a/interface/resources/qml/hifi/simplifiedUI/avatarApp/images/simplifiedAvatar_Magenta.png b/interface/resources/qml/hifi/simplifiedUI/avatarApp/images/simplifiedAvatar_Magenta.png new file mode 100644 index 0000000000..f01622dcd0 Binary files /dev/null and b/interface/resources/qml/hifi/simplifiedUI/avatarApp/images/simplifiedAvatar_Magenta.png differ diff --git a/interface/resources/qml/hifi/simplifiedUI/avatarApp/images/simplifiedAvatar_Red.png b/interface/resources/qml/hifi/simplifiedUI/avatarApp/images/simplifiedAvatar_Red.png new file mode 100644 index 0000000000..1e308d7f1f Binary files /dev/null and b/interface/resources/qml/hifi/simplifiedUI/avatarApp/images/simplifiedAvatar_Red.png differ diff --git a/interface/resources/qml/hifi/simplifiedUI/avatarApp/images/simplifiedAvatar_Yellow.png b/interface/resources/qml/hifi/simplifiedUI/avatarApp/images/simplifiedAvatar_Yellow.png new file mode 100644 index 0000000000..b123f358c0 Binary files /dev/null and b/interface/resources/qml/hifi/simplifiedUI/avatarApp/images/simplifiedAvatar_Yellow.png differ diff --git a/interface/resources/qml/hifi/simplifiedUI/topBar/SimplifiedTopBar.qml b/interface/resources/qml/hifi/simplifiedUI/topBar/SimplifiedTopBar.qml index 4919077dc3..65a5eb0c80 100644 --- a/interface/resources/qml/hifi/simplifiedUI/topBar/SimplifiedTopBar.qml +++ b/interface/resources/qml/hifi/simplifiedUI/topBar/SimplifiedTopBar.qml @@ -54,8 +54,8 @@ Rectangle { if ((MyAvatar.skeletonModelURL.indexOf("defaultAvatar") > -1 || MyAvatar.skeletonModelURL.indexOf("fst") === -1) && topBarInventoryModel.count > 0) { - Settings.setValue("simplifiedUI/alreadyAutoSelectedAvatar", true); - MyAvatar.useFullAvatarURL = topBarInventoryModel.get(0).download_url; + Settings.setValue("simplifiedUI/alreadyAutoSelectedAvatarFromInventory", true); + MyAvatar.useFullAvatarURL(topBarInventoryModel.get(0).download_url); } } } @@ -71,7 +71,7 @@ Rectangle { if (isLoggedIn) { Commerce.getWalletStatus(); } else { - // Show some error to the user + // Show some error to the user in the UI? } } @@ -113,12 +113,68 @@ Rectangle { topBarInventoryModel.getNextPage(); } else { inventoryFullyReceived = true; + var scriptExecutionCount = Settings.getValue("simplifiedUI/SUIScriptExecutionCount"); + var currentAvatarURL = MyAvatar.skeletonModelURL; + var currentAvatarURLContainsDefaultAvatar = currentAvatarURL.indexOf("defaultAvatar") > -1; + var currentAvatarURLContainsFST = currentAvatarURL.indexOf("fst") > -1; + var currentAvatarURLContainsSimplifiedAvatar = currentAvatarURL.indexOf("simplifiedAvatar") > -1; + var alreadyAutoSelectedAvatarFromInventory = Settings.getValue("simplifiedUI/alreadyAutoSelectedAvatarFromInventory", false); + var userHasValidAvatarInInventory = topBarInventoryModel.count > 0 && + topBarInventoryModel.get(0).download_url.indexOf(".fst") > -1; + var simplifiedAvatarPrefix = "https://content.highfidelity.com/Experiences/Releases/simplifiedUI/simplifiedFTUE/avatars/simplifiedAvatar_"; + var simplifiedAvatarColors = ["Blue", "Cyan", "Green", "Magenta", "Red"]; + var simplifiedAvatarSuffix = "/avatar.fst"; - // If we have an avatar in our inventory AND we haven't already auto-selected an avatar... - if ((!Settings.getValue("simplifiedUI/alreadyAutoSelectedAvatar", false) || - MyAvatar.skeletonModelURL.indexOf("defaultAvatar") > -1 || MyAvatar.skeletonModelURL.indexOf("fst") === -1) && topBarInventoryModel.count > 0) { - Settings.setValue("simplifiedUI/alreadyAutoSelectedAvatar", true); - MyAvatar.skeletonModelURL = topBarInventoryModel.get(0).download_url; + // Use `Settings.setValue("simplifiedUI/debugFTUE", 0);` to turn off FTUE Debug Mode. + // Use `Settings.setValue("simplifiedUI/debugFTUE", 1);` to debug FTUE Screen 1. + // Use `Settings.setValue("simplifiedUI/debugFTUE", 2);` to debug FTUE Screen 2. + // Use `Settings.setValue("simplifiedUI/debugFTUE", 3);` to debug FTUE Screen 3. + // Use `Settings.setValue("simplifiedUI/debugFTUE", 4);` to force the UI to show what would happen if the user had an empty Inventory. + + var debugFTUE = Settings.getValue("simplifiedUI/debugFTUE", 0); + if (debugFTUE === 1 || debugFTUE === 2) { + scriptExecutionCount = 1; + currentAvatarURLContainsDefaultAvatar = true; + if (debugFTUE === 1) { + userHasValidAvatarInInventory = false; + currentAvatarURLContainsSimplifiedAvatar = false; + } + } else if (debugFTUE === 3) { + scriptExecutionCount = 2; + currentAvatarURLContainsDefaultAvatar = false; + currentAvatarURLContainsSimplifiedAvatar = true; + } + + // If we have never auto-selected and the user is still using a default avatar or if the current avatar is not valid (fst), or if + // the current avatar is the old default (Woody), use top avatar from inventory or one of the new defaults. + + // If the current avatar URL is invalid, OR the user is using the "default avatar" (Woody)... + if (!currentAvatarURLContainsFST || currentAvatarURLContainsDefaultAvatar) { + // If the user has a valid avatar in their inventory... + if (userHasValidAvatarInInventory) { + // ...use the first avatar in the user's inventory. + MyAvatar.useFullAvatarURL(topBarInventoryModel.get(0).download_url); + Settings.setValue("simplifiedUI/alreadyAutoSelectedAvatarFromInventory", true); + // Else if the user isn't wearing a "Simplified Avatar" + } else if (!currentAvatarURLContainsSimplifiedAvatar) { + // ...assign to the user a new "Simplified Avatar" (i.e. a simple avatar of random color) + var avatarColor = simplifiedAvatarColors[Math.floor(Math.random() * simplifiedAvatarColors.length)]; + var simplifiedAvatarModelURL = simplifiedAvatarPrefix + avatarColor + simplifiedAvatarSuffix; + MyAvatar.useFullAvatarURL(simplifiedAvatarModelURL); + currentAvatarURLContainsSimplifiedAvatar = true; + } + } + + if (scriptExecutionCount === 1) { + sendToScript({ + "source": "SimplifiedTopBar.qml", + "method": "displayInitialLaunchWindow" + }); + } else if (scriptExecutionCount === 2 && currentAvatarURLContainsSimplifiedAvatar) { + sendToScript({ + "source": "SimplifiedTopBar.qml", + "method": "displaySecondLaunchWindow" + }); } } } @@ -556,7 +612,7 @@ Rectangle { } - function updatePreviewUrl() { + function updatePreviewUrl() { var previewUrl = ""; var downloadUrl = ""; for (var i = 0; i < topBarInventoryModel.count; ++i) { @@ -570,6 +626,8 @@ Rectangle { return; } } + + avatarButtonImage.source = "../images/defaultAvatar.svg"; } diff --git a/interface/src/scripting/DesktopScriptingInterface.cpp b/interface/src/scripting/DesktopScriptingInterface.cpp index 95d3bae332..32b5eb768d 100644 --- a/interface/src/scripting/DesktopScriptingInterface.cpp +++ b/interface/src/scripting/DesktopScriptingInterface.cpp @@ -52,6 +52,21 @@ static const QVariantMap DOCK_AREA { { "RIGHT", DockArea::RIGHT } }; +/**jsdoc + * The possible "relative position anchors" of an <code>InteractiveWindow</code>. Used when defining the `relativePosition` property of an `InteractiveWindow`. + * @typedef {object} InteractiveWindow.RelativePositionAnchors + * @property {InteractiveWindow.RelativePositionAnchor} TOP_LEFT - Specifies that the `relativePosition` of the `InteractiveWindow` will be offset from the top left of the Interface window. + * @property {InteractiveWindow.RelativePositionAnchor} TOP_RIGHT - Specifies that the `relativePosition` of the `InteractiveWindow` will be offset from the top right of the Interface window. + * @property {InteractiveWindow.RelativePositionAnchor} BOTTOM_RIGHT - Specifies that the `relativePosition` of the `InteractiveWindow` will be offset from the bottom right of the Interface window. + * @property {InteractiveWindow.RelativePositionAnchor} BOTTOM_LEFT - Specifies that the `relativePosition` of the `InteractiveWindow` will be offset from the bottom left of the Interface window. + */ +static const QVariantMap RELATIVE_POSITION_ANCHOR { + { "TOP_LEFT", RelativePositionAnchor::TOP_LEFT }, + { "TOP_RIGHT", RelativePositionAnchor::TOP_RIGHT }, + { "BOTTOM_RIGHT", RelativePositionAnchor::BOTTOM_RIGHT }, + { "BOTTOM_LEFT", RelativePositionAnchor::BOTTOM_LEFT } +}; + DesktopScriptingInterface::DesktopScriptingInterface(QObject* parent, bool restricted) : QObject(parent), _restricted(restricted) { } @@ -99,6 +114,10 @@ QVariantMap DesktopScriptingInterface::getDockArea() { return DOCK_AREA; } +QVariantMap DesktopScriptingInterface::getRelativePositionAnchor() { + return RELATIVE_POSITION_ANCHOR; +} + void DesktopScriptingInterface::setHUDAlpha(float alpha) { qApp->getApplicationCompositor().setAlpha(alpha); } diff --git a/interface/src/scripting/DesktopScriptingInterface.h b/interface/src/scripting/DesktopScriptingInterface.h index e562a32543..c25f382891 100644 --- a/interface/src/scripting/DesktopScriptingInterface.h +++ b/interface/src/scripting/DesktopScriptingInterface.h @@ -42,6 +42,9 @@ * @property {InteractiveWindow.DockAreas} DockArea - The possible docking locations of an {@link InteractiveWindow}: top, * bottom, left, or right of the Interface window. * <em>Read-only.</em> + * @property {InteractiveWindow.RelativePositionAnchors} RelativePositionAnchor - The possible "relative position anchors" for an {@link InteractiveWindow}: top left, + * top right, bottom right, or bottom left of the Interface window. + * <em>Read-only.</em> */ class DesktopScriptingInterface : public QObject, public Dependency { Q_OBJECT @@ -50,6 +53,7 @@ class DesktopScriptingInterface : public QObject, public Dependency { Q_PROPERTY(QVariantMap PresentationMode READ getPresentationMode CONSTANT FINAL) Q_PROPERTY(QVariantMap DockArea READ getDockArea CONSTANT FINAL) + Q_PROPERTY(QVariantMap RelativePositionAnchor READ getRelativePositionAnchor CONSTANT FINAL) Q_PROPERTY(int ALWAYS_ON_TOP READ flagAlwaysOnTop CONSTANT FINAL) Q_PROPERTY(int CLOSE_BUTTON_HIDES READ flagCloseButtonHides CONSTANT FINAL) @@ -106,7 +110,7 @@ private: Q_INVOKABLE InteractiveWindowPointer createWindowOnThread(const QString& sourceUrl, const QVariantMap& properties, QThread* targetThread); static QVariantMap getDockArea(); - + static QVariantMap getRelativePositionAnchor(); Q_INVOKABLE static QVariantMap getPresentationMode(); const bool _restricted; }; diff --git a/interface/src/ui/InteractiveWindow.cpp b/interface/src/ui/InteractiveWindow.cpp index e63c392a47..b7140b4009 100644 --- a/interface/src/ui/InteractiveWindow.cpp +++ b/interface/src/ui/InteractiveWindow.cpp @@ -39,6 +39,9 @@ static const char* const ADDITIONAL_FLAGS_PROPERTY = "additionalFlags"; static const char* const OVERRIDE_FLAGS_PROPERTY = "overrideFlags"; static const char* const SOURCE_PROPERTY = "source"; static const char* const TITLE_PROPERTY = "title"; +static const char* const RELATIVE_POSITION_ANCHOR_PROPERTY = "relativePositionAnchor"; +static const char* const RELATIVE_POSITION_PROPERTY = "relativePosition"; +static const char* const IS_FULL_SCREEN_WINDOW = "isFullScreenWindow"; static const char* const POSITION_PROPERTY = "position"; static const char* const INTERACTIVE_WINDOW_POSITION_PROPERTY = "interactiveWindowPosition"; static const char* const SIZE_PROPERTY = "size"; @@ -112,6 +115,14 @@ void InteractiveWindow::forwardKeyReleaseEvent(int key, int modifiers) { QCoreApplication::postEvent(QCoreApplication::instance(), event); } +void InteractiveWindow::onMainWindowGeometryChanged(QRect geometry) { + if (_isFullScreenWindow) { + repositionAndResizeFullScreenWindow(); + } else { + setPositionUsingRelativePositionAndAnchor(geometry); + } +} + void InteractiveWindow::emitMainWindowResizeEvent() { emit qApp->getWindow()->windowGeometryChanged(qApp->getWindow()->geometry()); } @@ -184,22 +195,32 @@ InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap */ if (nativeWindowInfo.contains(DOCK_AREA_PROPERTY)) { DockArea dockedArea = (DockArea) nativeWindowInfo[DOCK_AREA_PROPERTY].toInt(); + int tempWidth = 0; + int tempHeight = 0; switch (dockedArea) { case DockArea::TOP: dockArea = Qt::TopDockWidgetArea; - _dockWidget->setFixedHeight(windowSize.height()); + tempHeight = windowSize.height(); + _dockWidget->setFixedHeight(tempHeight); + qApp->getWindow()->setDockedWidgetRelativePositionOffset(QSize(0, -tempHeight)); break; case DockArea::BOTTOM: dockArea = Qt::BottomDockWidgetArea; - _dockWidget->setFixedHeight(windowSize.height()); + tempHeight = windowSize.height(); + _dockWidget->setFixedHeight(tempHeight); + qApp->getWindow()->setDockedWidgetRelativePositionOffset(QSize(0, tempHeight)); break; case DockArea::LEFT: dockArea = Qt::LeftDockWidgetArea; - _dockWidget->setFixedWidth(windowSize.width()); + tempWidth = windowSize.width(); + _dockWidget->setFixedWidth(tempWidth); + qApp->getWindow()->setDockedWidgetRelativePositionOffset(QSize(-tempWidth, 0)); break; case DockArea::RIGHT: dockArea = Qt::RightDockWidgetArea; - _dockWidget->setFixedWidth(windowSize.width()); + tempWidth = windowSize.width(); + _dockWidget->setFixedWidth(tempWidth); + qApp->getWindow()->setDockedWidgetRelativePositionOffset(QSize(tempWidth, 0)); break; default: @@ -255,6 +276,9 @@ InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap if (properties.contains(TITLE_PROPERTY)) { object->setProperty(TITLE_PROPERTY, properties[TITLE_PROPERTY].toString()); } + if (properties.contains(VISIBLE_PROPERTY)) { + object->setProperty(VISIBLE_PROPERTY, properties[INTERACTIVE_WINDOW_VISIBLE_PROPERTY].toBool()); + } if (properties.contains(SIZE_PROPERTY)) { const auto size = vec2FromVariant(properties[SIZE_PROPERTY]); object->setProperty(INTERACTIVE_WINDOW_SIZE_PROPERTY, QSize(size.x, size.y)); @@ -263,8 +287,21 @@ InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap const auto position = vec2FromVariant(properties[POSITION_PROPERTY]); object->setProperty(INTERACTIVE_WINDOW_POSITION_PROPERTY, QPointF(position.x, position.y)); } - if (properties.contains(VISIBLE_PROPERTY)) { - object->setProperty(VISIBLE_PROPERTY, properties[INTERACTIVE_WINDOW_VISIBLE_PROPERTY].toBool()); + if (properties.contains(RELATIVE_POSITION_ANCHOR_PROPERTY)) { + _relativePositionAnchor = static_cast<RelativePositionAnchor>(properties[RELATIVE_POSITION_ANCHOR_PROPERTY].toInt()); + } + if (properties.contains(RELATIVE_POSITION_PROPERTY)) { + _relativePosition = vec2FromVariant(properties[RELATIVE_POSITION_PROPERTY]); + setPositionUsingRelativePositionAndAnchor(qApp->getWindow()->geometry()); + } + if (properties.contains(IS_FULL_SCREEN_WINDOW)) { + _isFullScreenWindow = properties[IS_FULL_SCREEN_WINDOW].toBool(); + } + + if (_isFullScreenWindow) { + QRect geo = qApp->getWindow()->geometry(); + object->setProperty(INTERACTIVE_WINDOW_POSITION_PROPERTY, QPointF(geo.x(), geo.y())); + object->setProperty(INTERACTIVE_WINDOW_SIZE_PROPERTY, QSize(geo.width(), geo.height())); } // The qmlToScript method handles the thread-safety of this call. Because the QVariant argument @@ -288,6 +325,8 @@ InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap connect(object, SIGNAL(interactiveWindowVisibleChanged()), this, SLOT(parentNativeWindowToMainWindow()), Qt::QueuedConnection); connect(object, SIGNAL(presentationModeChanged()), this, SLOT(parentNativeWindowToMainWindow()), Qt::QueuedConnection); #endif + + connect(qApp->getWindow(), &MainWindow::windowGeometryChanged, this, &InteractiveWindow::onMainWindowGeometryChanged, Qt::QueuedConnection); QUrl sourceURL{ sourceUrl }; // If the passed URL doesn't correspond to a known scheme, assume it's a local file path @@ -414,6 +453,68 @@ void InteractiveWindow::setPosition(const glm::vec2& position) { } } +RelativePositionAnchor InteractiveWindow::getRelativePositionAnchor() const { + return _relativePositionAnchor; +} + +void InteractiveWindow::setRelativePositionAnchor(const RelativePositionAnchor& relativePositionAnchor) { + _relativePositionAnchor = relativePositionAnchor; + setPositionUsingRelativePositionAndAnchor(qApp->getWindow()->geometry()); +} + +glm::vec2 InteractiveWindow::getRelativePosition() const { + return _relativePosition; +} + +void InteractiveWindow::setRelativePosition(const glm::vec2& relativePosition) { + _relativePosition = relativePosition; + setPositionUsingRelativePositionAndAnchor(qApp->getWindow()->geometry()); +} + +void InteractiveWindow::setPositionUsingRelativePositionAndAnchor(const QRect& mainWindowGeometry) { + RelativePositionAnchor relativePositionAnchor = getRelativePositionAnchor(); + glm::vec2 relativePosition = getRelativePosition(); + + glm::vec2 newPosition; + + switch (relativePositionAnchor) { + case RelativePositionAnchor::TOP_LEFT: + newPosition.x = mainWindowGeometry.x() + relativePosition.x; + newPosition.y = mainWindowGeometry.y() + relativePosition.y; + break; + case RelativePositionAnchor::TOP_RIGHT: + newPosition.x = mainWindowGeometry.x() + mainWindowGeometry.width() - relativePosition.x; + newPosition.y = mainWindowGeometry.y() + relativePosition.y; + break; + case RelativePositionAnchor::BOTTOM_RIGHT: + newPosition.x = mainWindowGeometry.x() + mainWindowGeometry.width() - relativePosition.x; + newPosition.y = mainWindowGeometry.y() + mainWindowGeometry.height() - relativePosition.y; + break; + case RelativePositionAnchor::BOTTOM_LEFT: + newPosition.x = mainWindowGeometry.x() + relativePosition.x; + newPosition.y = mainWindowGeometry.y() + mainWindowGeometry.height() - relativePosition.y; + break; + } + + // Make sure we include the dimensions of the docked widget! + QSize dockedWidgetRelativePositionOffset = qApp->getWindow()->getDockedWidgetRelativePositionOffset(); + newPosition.x = newPosition.x + dockedWidgetRelativePositionOffset.width(); + newPosition.y = newPosition.y + dockedWidgetRelativePositionOffset.height(); + + if (_qmlWindowProxy) { + QMetaObject::invokeMethod(_qmlWindowProxy.get(), "writeProperty", Q_ARG(QString, INTERACTIVE_WINDOW_POSITION_PROPERTY), + Q_ARG(QVariant, QPointF(newPosition.x, newPosition.y))); + } + setPosition(newPosition); +} + +void InteractiveWindow::repositionAndResizeFullScreenWindow() { + QRect windowGeometry = qApp->getWindow()->geometry(); + + setPosition(glm::vec2(windowGeometry.x(), windowGeometry.y())); + setSize(glm::vec2(windowGeometry.width(), windowGeometry.height())); +} + glm::vec2 InteractiveWindow::getSize() const { if (!_qmlWindowProxy) { return {}; diff --git a/interface/src/ui/InteractiveWindow.h b/interface/src/ui/InteractiveWindow.h index ba53684173..d25c3d7ec2 100644 --- a/interface/src/ui/InteractiveWindow.h +++ b/interface/src/ui/InteractiveWindow.h @@ -89,6 +89,14 @@ namespace InteractiveWindowEnums { RIGHT }; Q_ENUM_NS(DockArea); + + enum RelativePositionAnchor { + TOP_LEFT, + TOP_RIGHT, + BOTTOM_RIGHT, + BOTTOM_LEFT + }; + Q_ENUM_NS(RelativePositionAnchor); } using namespace InteractiveWindowEnums; @@ -121,6 +129,8 @@ class InteractiveWindow : public QObject { Q_PROPERTY(QString title READ getTitle WRITE setTitle) Q_PROPERTY(glm::vec2 position READ getPosition WRITE setPosition) + Q_PROPERTY(RelativePositionAnchor relativePositionAnchor READ getRelativePositionAnchor WRITE setRelativePositionAnchor) + Q_PROPERTY(glm::vec2 relativePosition READ getRelativePosition WRITE setRelativePosition) Q_PROPERTY(glm::vec2 size READ getSize WRITE setSize) Q_PROPERTY(bool visible READ isVisible WRITE setVisible) Q_PROPERTY(int presentationMode READ getPresentationMode WRITE setPresentationMode) @@ -136,6 +146,21 @@ private: Q_INVOKABLE glm::vec2 getPosition() const; Q_INVOKABLE void setPosition(const glm::vec2& position); + + RelativePositionAnchor _relativePositionAnchor{ RelativePositionAnchor::TOP_LEFT }; + Q_INVOKABLE RelativePositionAnchor getRelativePositionAnchor() const; + Q_INVOKABLE void setRelativePositionAnchor(const RelativePositionAnchor& position); + + // This "relative position" is relative to the "relative position anchor" and excludes the window frame. + // This position will ALWAYS include the geometry of a docked widget, if one is present. + glm::vec2 _relativePosition{ 0.0f, 0.0f }; + Q_INVOKABLE glm::vec2 getRelativePosition() const; + Q_INVOKABLE void setRelativePosition(const glm::vec2& position); + + Q_INVOKABLE void setPositionUsingRelativePositionAndAnchor(const QRect& mainWindowGeometry); + + bool _isFullScreenWindow{ false }; + Q_INVOKABLE void repositionAndResizeFullScreenWindow(); Q_INVOKABLE glm::vec2 getSize() const; Q_INVOKABLE void setSize(const glm::vec2& size); @@ -320,6 +345,7 @@ protected slots: void forwardKeyPressEvent(int key, int modifiers); void forwardKeyReleaseEvent(int key, int modifiers); void emitMainWindowResizeEvent(); + void onMainWindowGeometryChanged(QRect geometry); private: std::shared_ptr<QmlWindowProxy> _qmlWindowProxy; diff --git a/libraries/ui/src/MainWindow.h b/libraries/ui/src/MainWindow.h index 543f8ce9af..b37ac2ec1b 100644 --- a/libraries/ui/src/MainWindow.h +++ b/libraries/ui/src/MainWindow.h @@ -24,6 +24,10 @@ public: ~MainWindow(); static QWindow* findMainWindow(); + + // This offset is used for positioning children window relative to the main window. + void setDockedWidgetRelativePositionOffset(const QSize& newOffset) { _dockedWidgetRelativePositionOffset.setWidth(newOffset.width()); _dockedWidgetRelativePositionOffset.setHeight(newOffset.height()); } + QSize getDockedWidgetRelativePositionOffset() { return _dockedWidgetRelativePositionOffset; } public slots: void restoreGeometry(); void saveGeometry(); @@ -46,6 +50,7 @@ protected: private: Setting::Handle<QRect> _windowGeometry; Setting::Handle<int> _windowState; + QSize _dockedWidgetRelativePositionOffset{ 0, 0 }; }; #endif /* defined(__hifi__MainWindow__) */ diff --git a/scripts/simplifiedUI/simplifiedEmote/simplifiedEmote.js b/scripts/simplifiedUI/simplifiedEmote/simplifiedEmote.js index 8633fe8870..d7d6279e10 100644 --- a/scripts/simplifiedUI/simplifiedEmote/simplifiedEmote.js +++ b/scripts/simplifiedUI/simplifiedEmote/simplifiedEmote.js @@ -444,17 +444,9 @@ function updateEmoteIndicatorIcon(iconURL) { } -function onGeometryChanged(rect) { - updateEmoteAppBarPosition(); -} - - function onWindowMinimizedChanged(isMinimized) { - if (isMinimized) { - handleEmoteIndicatorVisibleChanged(false); - } else if (!HMD.active) { - handleEmoteIndicatorVisibleChanged(true); - } + isWindowMinimized = isMinimized; + maybeChangeEmoteIndicatorVisibility(!isMinimized); } @@ -539,10 +531,11 @@ function showEmoteAppBar() { x: EMOTE_APP_BAR_WIDTH_PX, y: EMOTE_APP_BAR_HEIGHT_PX }, - position: { - x: Window.x + EMOTE_APP_BAR_LEFT_MARGIN, - y: Window.y + Window.innerHeight - EMOTE_APP_BAR_BOTTOM_MARGIN + relativePosition: { + x: EMOTE_APP_BAR_LEFT_MARGIN, + y: EMOTE_APP_BAR_BOTTOM_MARGIN }, + relativePositionAnchor: Desktop.RelativePositionAnchor.BOTTOM_LEFT, overrideFlags: EMOTE_APP_BAR_WINDOW_FLAGS }); @@ -550,10 +543,18 @@ function showEmoteAppBar() { } -function handleEmoteIndicatorVisibleChanged(shouldBeVisible) { - if (shouldBeVisible && !emoteAppBarWindow) { +// There is currently no property in the Window Scripting Interface to determine +// whether the Interface window is currently minimized. This feels like an oversight. +// We should add that functionality to the Window Scripting Interface, and remove `isWindowMinimized` below. +var isWindowMinimized = false; +function maybeChangeEmoteIndicatorVisibility(desiredVisibility) { + if (isWindowMinimized || HMD.active) { + desiredVisibility = false; + } + + if (desiredVisibility && !emoteAppBarWindow) { showEmoteAppBar(); - } else if (emoteAppBarWindow) { + } else if (!desiredVisibility && emoteAppBarWindow) { emoteAppBarWindow.fromQml.disconnect(onMessageFromEmoteAppBar); emoteAppBarWindow.close(); emoteAppBarWindow = false; @@ -561,23 +562,25 @@ function handleEmoteIndicatorVisibleChanged(shouldBeVisible) { } +function handleFTUEScreensVisibilityChanged(ftueScreenVisible) { + maybeChangeEmoteIndicatorVisibility(!ftueScreenVisible); +} + + function onDisplayModeChanged(isHMDMode) { reactionsBegun.forEach(function(react) { endReactionWrapper(react); }); - if (isHMDMode) { - handleEmoteIndicatorVisibleChanged(false); - } else { - handleEmoteIndicatorVisibleChanged(true); - } + maybeChangeEmoteIndicatorVisibility(!isHMDMode); } -var emojiAPI = Script.require("./emojiApp/simplifiedEmoji.js?" + Date.now()); +var emojiAPI = Script.require("./emojiApp/simplifiedEmoji.js"); var keyPressSignalsConnected = false; var emojiCodeMap; var customEmojiCodeMap; +var _this; function setup() { deleteOldReticles(); @@ -605,16 +608,25 @@ function setup() { }, {}); Window.minimizedChanged.connect(onWindowMinimizedChanged); - Window.geometryChanged.connect(onGeometryChanged); HMD.displayModeChanged.connect(onDisplayModeChanged); getSounds(); - handleEmoteIndicatorVisibleChanged(true); + maybeChangeEmoteIndicatorVisibility(true); Controller.keyPressEvent.connect(keyPressHandler); Controller.keyReleaseEvent.connect(keyReleaseHandler); keyPressSignalsConnected = true; Script.scriptEnding.connect(unload); + + function Emote() { + _this = this; + } + + Emote.prototype = { + handleFTUEScreensVisibilityChanged: handleFTUEScreensVisibilityChanged + }; + + return new Emote(); } @@ -638,7 +650,6 @@ function unload() { maybeDeleteRemoteIndicatorTimeout(); Window.minimizedChanged.disconnect(onWindowMinimizedChanged); - Window.geometryChanged.disconnect(onGeometryChanged); HMD.displayModeChanged.disconnect(onDisplayModeChanged); if (keyPressSignalsConnected) { @@ -671,7 +682,6 @@ function unload() { // #region EMOJI_UTILITY -var EMOJI_52_BASE_URL = "../../resources/images/emojis/52px/"; function selectedEmoji(code) { emojiAPI.addEmoji(code); // this URL needs to be relative to SimplifiedEmoteIndicator.qml @@ -786,4 +796,6 @@ function toggleEmojiApp() { // END EMOJI // ************************************* -setup(); \ No newline at end of file +var emote = setup(); + +module.exports = emote; \ No newline at end of file diff --git a/scripts/simplifiedUI/ui/simplifiedFTUE/InitialLaunchWindow.qml b/scripts/simplifiedUI/ui/simplifiedFTUE/InitialLaunchWindow.qml new file mode 100644 index 0000000000..1938586edb --- /dev/null +++ b/scripts/simplifiedUI/ui/simplifiedFTUE/InitialLaunchWindow.qml @@ -0,0 +1,350 @@ +// +// InitialLaunchWindow.qml +// +// Copyright 2019 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.10 +import QtQuick.Controls 2.3 +import QtGraphicalEffects 1.0 +import QtQuick.Layouts 1.3 +import stylesUit 1.0 as HifiStylesUit +import TabletScriptingInterface 1.0 +import hifi.simplifiedUI.simplifiedConstants 1.0 as SimplifiedConstants +import hifi.simplifiedUI.simplifiedControls 1.0 as SimplifiedControls + +Rectangle { + id: root + color: simplifiedUI.colors.white + anchors.fill: parent + property bool landscapeOrientation: root.width > root.height + + SimplifiedConstants.SimplifiedConstants { + id: simplifiedUI + } + + Component.onCompleted: { + var debugFTUE = Settings.getValue("simplifiedUI/debugFTUE", 0); + + if ((debugFTUE !== 1 && + (Settings.getValue("simplifiedUI/alreadyAutoSelectedAvatarFromInventory", false) || + Settings.getValue("simplifiedUI/closedAvatarPageOfInitialLaunchWindow", false))) || + debugFTUE === 2) { + tempAvatarPageContainer.visible = false; + controlsContainer.visible = true; + } + } + + Item { + id: tempAvatarPageContainer + anchors.fill: parent + + Item { + id: contentContainer + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: firstPageBottomBarContainer.top + + Image { + id: avatarImage + anchors.verticalCenter: parent.verticalCenter + height: Math.max(parent.height - 48, 350) + anchors.left: parent.left + anchors.leftMargin: 12 + source: resourceDirectoryUrl + "qml/hifi/simplifiedUI/avatarApp/images/" + + MyAvatar.skeletonModelURL.substring(MyAvatar.skeletonModelURL.indexOf("simplifiedAvatar"), MyAvatar.skeletonModelURL.lastIndexOf("/")) + ".png" + mipmap: true + fillMode: Image.PreserveAspectFit + } + + Flickable { + id: textContainer + clip: true + anchors.top: parent.top + anchors.topMargin: 128 + anchors.bottom: qrAndInstructionsContainer.top + anchors.bottomMargin: 32 + anchors.left: avatarImage.right + anchors.leftMargin: 48 + anchors.right: parent.right + contentWidth: width + contentHeight: contentItem.childrenRect.height + interactive: contentHeight > height + + HifiStylesUit.RalewayBold { + id: headerText + text: "We know this isn't you..." + color: simplifiedUI.colors.text.black + size: 48 + height: paintedHeight + wrapMode: Text.Wrap + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 16 + } + + HifiStylesUit.RalewayRegular { + id: descriptionText + anchors.top: headerText.bottom + anchors.topMargin: 10 + anchors.left: parent.left + width: Math.min(700, parent.width) - headerText.anchors.rightMargin + height: paintedHeight + text: "...but we've given you this <b>temporary avatar</b> to use " + + "for today. If you see this avatar in-world, walk up and " + + "say hello to other new users!<br><br>" + + "<b>We want you to be you</b> so we've built " + + '<a href="https://www.highfidelity.com/knowledge/virtual-you/">Virtual You</a>, an Avatar Creator ' + + "App. Creating an avatar is as easy as taking a selfie and picking your " + + "outfits! Available now on iOS and Android." + color: simplifiedUI.colors.text.black + size: 22 + wrapMode: Text.Wrap + + onLinkActivated: { + Qt.openUrlExternally(link); + } + } + } + + Item { + id: qrAndInstructionsContainer + anchors.left: avatarImage.right + anchors.leftMargin: 48 + anchors.right: parent.right + anchors.rightMargin: 16 + anchors.bottom: parent.bottom + height: 130 + + Image { + id: avatarAppQRCodeImage + source: resourceDirectoryUrl + "qml/hifi/simplifiedUI/avatarApp/images/qrCode.jpg" + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: parent.left + width: 130 + mipmap: true + fillMode: Image.PreserveAspectFit + } + + HifiStylesUit.RalewayBold { + id: instructionText + anchors.top: avatarAppQRCodeImage.top + anchors.bottom: avatarAppQRCodeImage.bottom + anchors.left: avatarAppQRCodeImage.right + anchors.leftMargin: 30 + anchors.right: parent.right + text: "Use your mobile phone to scan this QR code." + color: simplifiedUI.colors.text.black + size: 22 + wrapMode: Text.Wrap + } + } + + SimplifiedControls.VerticalScrollBar { + parent: textContainer + visible: parent.contentHeight > parent.height + size: parent.height / parent.contentHeight + } + } + + Item { + id: firstPageBottomBarContainer + anchors.left: parent.left + anchors.leftMargin: 32 + anchors.right: parent.right + anchors.rightMargin: 32 + anchors.bottom: parent.bottom + height: continueLink.height + 48 + + HifiStylesUit.RalewayBold { + id: continueLink + anchors.centerIn: parent + text: "Continue >" + width: parent.width + height: paintedHeight + color: simplifiedUI.colors.text.lightBlue + opacity: continueMouseArea.containsMouse ? 1.0 : 0.7 + size: 36 + wrapMode: Text.Wrap + horizontalAlignment: Text.AlignHCenter + + MouseArea { + id: continueMouseArea + hoverEnabled: true + anchors.fill: parent + + onClicked: { + Tablet.playSound(TabletEnums.ButtonClick); + tempAvatarPageContainer.visible = false; + Settings.setValue("simplifiedUI/closedAvatarPageOfInitialLaunchWindow", true); + controlsContainer.visible = true; + } + } + } + } + } + + Item { + id: controlsContainer + visible: false + anchors.fill: parent + + HifiStylesUit.RalewayRegular { + id: controlsDescriptionText + text: "Use these avatar controls to<br><b>interact with and move around in your new HQ.</b>" + anchors.top: parent.top + anchors.topMargin: 48 + anchors.left: parent.left + anchors.leftMargin: 32 + anchors.right: parent.right + anchors.rightMargin: 32 + horizontalAlignment: Text.AlignHCenter + height: paintedHeight + color: simplifiedUI.colors.text.black + size: 36 + wrapMode: Text.Wrap + } + + Item { + anchors.top: controlsDescriptionText.bottom + anchors.topMargin: 16 + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: bottomBarContainer.top + + GridView { + id: controlsGrid + property int maxColumns: 2 + property int idealCellWidth: 361 + anchors.fill: parent + clip: true + cellWidth: width / Math.min(Math.floor(width / idealCellWidth), maxColumns) + cellHeight: 225 + model: ListModel { + ListElement { + imageHeight: 198 + imageSource: "images/walkingControls.png" + } + ListElement { + imageHeight: 193 + imageSource: "images/mouseControls.png" + } + ListElement { + imageHeight: 146 + imageSource: "images/runJumpControls.png" + } + ListElement { + imageHeight: 96 + imageSource: "images/cameraControls.png" + } + } + delegate: Rectangle { + height: GridView.view.cellHeight + width: GridView.view.cellWidth + Image { + anchors.centerIn: parent + width: parent.GridView.view.idealCellWidth + height: model.imageHeight + source: model.imageSource + fillMode: Image.PreserveAspectFit + } + } + } + + SimplifiedControls.VerticalScrollBar { + parent: controlsGrid + anchors.topMargin: 96 + anchors.bottomMargin: anchors.topMargin + } + } + + Item { + id: bottomBarContainer + anchors.left: parent.left + anchors.leftMargin: 32 + anchors.right: parent.right + anchors.rightMargin: 32 + anchors.bottom: parent.bottom + height: iHaveAGoodGrip.height + learnMoreLink.height + 48 + + HifiStylesUit.RalewayBold { + id: iHaveAGoodGrip + anchors.centerIn: parent + text: "I've got a good grip on the controls." + width: parent.width + height: paintedHeight + color: simplifiedUI.colors.text.lightBlue + opacity: goodGripMouseArea.containsMouse ? 1.0 : 0.7 + size: 36 + wrapMode: Text.Wrap + horizontalAlignment: Text.AlignHCenter + + MouseArea { + id: goodGripMouseArea + hoverEnabled: true + anchors.fill: parent + + onClicked: { + Tablet.playSound(TabletEnums.ButtonClick); + sendToScript({ + "source": "InitialLaunchWindow.qml", + "method": "closeInitialLaunchWindow" + }); + } + } + } + + HifiStylesUit.RalewayBold { + id: learnMoreLink + anchors.left: parent.left + anchors.leftMargin: 16 + anchors.top: iHaveAGoodGrip.bottom + anchors.topMargin: 8 + text: "Learn more about our controls." + width: paintedWidth + height: paintedHeight + color: simplifiedUI.colors.text.lightBlue + opacity: learnMoreAboutControlsMouseArea.containsMouse ? 1.0 : 0.7 + size: 14 + wrapMode: Text.Wrap + + MouseArea { + id: learnMoreAboutControlsMouseArea + hoverEnabled: true + anchors.fill: parent + + onClicked: { + Tablet.playSound(TabletEnums.ButtonClick); + Qt.openUrlExternally("https://www.highfidelity.com/knowledge/get-around"); + } + } + } + } + } + + Image { + id: topLeftAccentImage + width: 400 + height: 180 + anchors.left: parent.left + anchors.top: parent.top + source: "images/defaultTopLeft.png" + } + + Image { + id: bottomRightAccentImage + width: 80 + height: 250 + anchors.right: parent.right + anchors.bottom: parent.bottom + source: "images/defaultBottomRight.png" + } + + signal sendToScript(var message); +} diff --git a/scripts/simplifiedUI/ui/simplifiedFTUE/SecondLaunchWindow.qml b/scripts/simplifiedUI/ui/simplifiedFTUE/SecondLaunchWindow.qml new file mode 100644 index 0000000000..2a796465ae --- /dev/null +++ b/scripts/simplifiedUI/ui/simplifiedFTUE/SecondLaunchWindow.qml @@ -0,0 +1,186 @@ +// +// SecondLaunchWindow.qml +// +// Copyright 2019 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.10 +import QtQuick.Controls 2.3 +import QtGraphicalEffects 1.0 +import QtQuick.Layouts 1.3 +import stylesUit 1.0 as HifiStylesUit +import TabletScriptingInterface 1.0 +import hifi.simplifiedUI.simplifiedConstants 1.0 as SimplifiedConstants +import hifi.simplifiedUI.simplifiedControls 1.0 as SimplifiedControls + +Rectangle { + id: root + color: simplifiedUI.colors.white + anchors.fill: parent + + SimplifiedConstants.SimplifiedConstants { + id: simplifiedUI + } + + Item { + id: contentContainer + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: continueLink.top + + Image { + id: avatarImage + anchors.verticalCenter: parent.verticalCenter + height: Math.max(parent.height - 48, 350) + anchors.left: parent.left + anchors.leftMargin: 12 + source: resourceDirectoryUrl + "qml/hifi/simplifiedUI/avatarApp/images/hero.png" + mipmap: true + fillMode: Image.PreserveAspectFit + } + + Item { + anchors.top: parent.top + anchors.topMargin: 196 + anchors.bottom: parent.bottom + anchors.bottomMargin: 32 + anchors.left: avatarImage.right + anchors.leftMargin: 48 + anchors.right: parent.right + + Flickable { + id: textContainer + clip: true + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: parent.left + width: Math.min(700, parent.width) + contentWidth: width + contentHeight: contentItem.childrenRect.height + interactive: contentHeight > height + + HifiStylesUit.RalewayBold { + id: headerText + text: "Stand out from the crowd!" + color: simplifiedUI.colors.text.black + size: 48 + height: paintedHeight + wrapMode: Text.Wrap + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 16 + } + + HifiStylesUit.RalewayRegular { + id: descriptionText + anchors.top: headerText.bottom + anchors.topMargin: 10 + anchors.left: parent.left + width: parent.width - headerText.anchors.rightMargin + height: paintedHeight + text: "You can create and upload custom avatars from our Avatar Creator App. " + + "It's as easy as taking a selfie.<br>Available now on iOS and Android Platforms." + color: simplifiedUI.colors.text.black + size: 22 + wrapMode: Text.Wrap + } + + Item { + id: qrAndInstructionsContainer + anchors.top: descriptionText.bottom + anchors.topMargin: 24 + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 16 + height: avatarAppQRCodeImage.height + + Image { + id: avatarAppQRCodeImage + source: resourceDirectoryUrl + "qml/hifi/simplifiedUI/avatarApp/images/qrCode.jpg" + anchors.top: parent.top + anchors.left: parent.left + width: 130 + height: width + mipmap: true + fillMode: Image.PreserveAspectFit + } + + HifiStylesUit.RalewayBold { + id: instructionText + anchors.top: avatarAppQRCodeImage.top + anchors.bottom: avatarAppQRCodeImage.bottom + anchors.left: avatarAppQRCodeImage.right + anchors.leftMargin: 30 + anchors.right: parent.right + text: "Use your mobile phone to scan this QR code." + color: simplifiedUI.colors.text.black + size: 22 + wrapMode: Text.Wrap + } + } + } + } + + SimplifiedControls.VerticalScrollBar { + parent: textContainer + visible: parent.contentHeight > parent.height + size: parent.height / parent.contentHeight + } + } + + + HifiStylesUit.RalewayBold { + id: continueLink + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.leftMargin: 16 + anchors.right: parent.right + anchors.rightMargin: 16 + height: 96 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + text: "No thanks, I'll keep using my default avatar." + color: simplifiedUI.colors.text.lightBlue + opacity: continueMouseArea.containsMouse ? 1.0 : 0.7 + size: 24 + + MouseArea { + id: continueMouseArea + hoverEnabled: true + anchors.fill: parent + + onClicked: { + Tablet.playSound(TabletEnums.ButtonClick); + sendToScript({ + "source": "SecondLaunchWindow.qml", + "method": "closeSecondLaunchWindow" + }); + } + } + } + + Image { + id: topLeftAccentImage + width: 130 + height: 320 + anchors.left: parent.left + anchors.top: parent.top + source: "images/standOutTopLeft.png" + } + + Image { + id: bottomRightAccentImage + width: 250 + height: 80 + anchors.right: parent.right + anchors.bottom: parent.bottom + source: "images/standOutBottomRight.png" + } + + signal sendToScript(var message); +} diff --git a/scripts/simplifiedUI/ui/simplifiedFTUE/images/cameraControls.png b/scripts/simplifiedUI/ui/simplifiedFTUE/images/cameraControls.png new file mode 100644 index 0000000000..e54e26a3ba Binary files /dev/null and b/scripts/simplifiedUI/ui/simplifiedFTUE/images/cameraControls.png differ diff --git a/scripts/simplifiedUI/ui/simplifiedFTUE/images/defaultBottomRight.png b/scripts/simplifiedUI/ui/simplifiedFTUE/images/defaultBottomRight.png new file mode 100644 index 0000000000..1668347aa4 Binary files /dev/null and b/scripts/simplifiedUI/ui/simplifiedFTUE/images/defaultBottomRight.png differ diff --git a/scripts/simplifiedUI/ui/simplifiedFTUE/images/defaultTopLeft.png b/scripts/simplifiedUI/ui/simplifiedFTUE/images/defaultTopLeft.png new file mode 100644 index 0000000000..c863569d7f Binary files /dev/null and b/scripts/simplifiedUI/ui/simplifiedFTUE/images/defaultTopLeft.png differ diff --git a/scripts/simplifiedUI/ui/simplifiedFTUE/images/mouseControls.png b/scripts/simplifiedUI/ui/simplifiedFTUE/images/mouseControls.png new file mode 100644 index 0000000000..6354b1aeae Binary files /dev/null and b/scripts/simplifiedUI/ui/simplifiedFTUE/images/mouseControls.png differ diff --git a/scripts/simplifiedUI/ui/simplifiedFTUE/images/runJumpControls.png b/scripts/simplifiedUI/ui/simplifiedFTUE/images/runJumpControls.png new file mode 100644 index 0000000000..af0492475d Binary files /dev/null and b/scripts/simplifiedUI/ui/simplifiedFTUE/images/runJumpControls.png differ diff --git a/scripts/simplifiedUI/ui/simplifiedFTUE/images/standOutBottomRight.png b/scripts/simplifiedUI/ui/simplifiedFTUE/images/standOutBottomRight.png new file mode 100644 index 0000000000..8b9983bb88 Binary files /dev/null and b/scripts/simplifiedUI/ui/simplifiedFTUE/images/standOutBottomRight.png differ diff --git a/scripts/simplifiedUI/ui/simplifiedFTUE/images/standOutTopLeft.png b/scripts/simplifiedUI/ui/simplifiedFTUE/images/standOutTopLeft.png new file mode 100644 index 0000000000..30cb623f42 Binary files /dev/null and b/scripts/simplifiedUI/ui/simplifiedFTUE/images/standOutTopLeft.png differ diff --git a/scripts/simplifiedUI/ui/simplifiedFTUE/images/walkingControls.png b/scripts/simplifiedUI/ui/simplifiedFTUE/images/walkingControls.png new file mode 100644 index 0000000000..bedc6991bb Binary files /dev/null and b/scripts/simplifiedUI/ui/simplifiedFTUE/images/walkingControls.png differ diff --git a/scripts/simplifiedUI/ui/simplifiedUI.js b/scripts/simplifiedUI/ui/simplifiedUI.js index f0a76f4f5f..fc0dd9fddd 100644 --- a/scripts/simplifiedUI/ui/simplifiedUI.js +++ b/scripts/simplifiedUI/ui/simplifiedUI.js @@ -14,7 +14,6 @@ // START CONFIG OPTIONS -var DOCKED_QML_SUPPORTED = true; var TOOLBAR_NAME = "com.highfidelity.interface.toolbar.system"; var DEFAULT_SCRIPTS_PATH_PREFIX = ScriptDiscoveryService.defaultScriptsPath + "/"; // END CONFIG OPTIONS @@ -352,6 +351,124 @@ function setOutputMuted(outputMuted) { } } +var TOP_BAR_HEIGHT_PX = 48; +var INITIAL_LAUNCH_QML_PATH = Script.resolvePath("./simplifiedFTUE/InitialLaunchWindow.qml"); +var INITIAL_LAUNCH_WINDOW_TITLE = "Initial Launch"; +var INITIAL_LAUNCH_PRESENTATION_MODE = Desktop.PresentationMode.NATIVE; +var INITIAL_WINDOW_FLAGS = 0x00000001 | // Qt::Window +0x00000008 | // Qt::Popup +0x00000002 | // Qt::Tool +0x00000800 | // Qt::FramelessWindowHint +0x40000000; // Qt::NoDropShadowWindowHint +var initialLaunchWindow = false; +function displayInitialLaunchWindow() { + if (initialLaunchWindow) { + return; + } + + simplifiedEmote.handleFTUEScreensVisibilityChanged(true); + + initialLaunchWindow = Desktop.createWindow(INITIAL_LAUNCH_QML_PATH, { + title: INITIAL_LAUNCH_WINDOW_TITLE, + presentationMode: INITIAL_LAUNCH_PRESENTATION_MODE, + isFullScreenWindow: true, + overrideFlags: INITIAL_WINDOW_FLAGS + }); + + initialLaunchWindow.fromQml.connect(onMessageFromInitialLaunchWindow); + + Window.location = "file:///~/serverless/tutorial.json"; +} + +var SECOND_LAUNCH_QML_PATH = Script.resolvePath("simplifiedFTUE/SecondLaunchWindow.qml"); +var SECOND_LAUNCH_WINDOW_TITLE = "Second Launch"; +var SECOND_LAUNCH_PRESENTATION_MODE = Desktop.PresentationMode.NATIVE; +var SECOND_WINDOW_FLAGS = 0x00000001 | // Qt::Window +0x00000008 | // Qt::Popup +0x00000002 | // Qt::Tool +0x00000800 | // Qt::FramelessWindowHint +0x40000000; // Qt::NoDropShadowWindowHint +var secondLaunchWindow = false; +function displaySecondLaunchWindow() { + if (secondLaunchWindow) { + return; + } + + simplifiedEmote.handleFTUEScreensVisibilityChanged(true); + + secondLaunchWindow = Desktop.createWindow(SECOND_LAUNCH_QML_PATH, { + title: SECOND_LAUNCH_WINDOW_TITLE, + presentationMode: SECOND_LAUNCH_PRESENTATION_MODE, + isFullScreenWindow: true, + overrideFlags: SECOND_WINDOW_FLAGS + }); + + secondLaunchWindow.fromQml.connect(onMessageFromSecondLaunchWindow); + + Window.location = "file:///~/serverless/tutorial.json"; +} + +function closeInitialLaunchWindow() { + if (initialLaunchWindow) { + initialLaunchWindow.fromQml.disconnect(onMessageFromInitialLaunchWindow); + initialLaunchWindow.close(); + initialLaunchWindow = null; + } + + simplifiedEmote.handleFTUEScreensVisibilityChanged(false); +} + +function closeSecondLaunchWindow() { + if (secondLaunchWindow) { + secondLaunchWindow.fromQml.disconnect(onMessageFromSecondLaunchWindow); + secondLaunchWindow.close(); + secondLaunchWindow = null; + } + + simplifiedEmote.handleFTUEScreensVisibilityChanged(false); +} + +var INITIAL_LAUNCH_WINDOW_MESSAGE_SOURCE = "InitialLaunchWindow.qml"; +function onMessageFromInitialLaunchWindow(message) { + if (message.source !== INITIAL_LAUNCH_WINDOW_MESSAGE_SOURCE) { + return; + } + + switch (message.method) { + case "closeInitialLaunchWindow": + closeInitialLaunchWindow(); + var homeLocation = LocationBookmarks.getAddress("hqhome"); + if (homeLocation) { + Window.location = homeLocation; + } + break; + + default: + console.log("Unrecognized message from " + INITIAL_LAUNCH_WINDOW_MESSAGE_SOURCE + ": " + JSON.stringify(message)); + break; + } +} + +var SECOND_LAUNCH_WINDOW_MESSAGE_SOURCE = "SecondLaunchWindow.qml"; +function onMessageFromSecondLaunchWindow(message) { + if (message.source !== SECOND_LAUNCH_WINDOW_MESSAGE_SOURCE) { + return; + } + + switch (message.method) { + case "closeSecondLaunchWindow": + closeSecondLaunchWindow(); + var homeLocation = LocationBookmarks.getAddress("hqhome"); + if (homeLocation) { + Window.location = homeLocation; + } + break; + + default: + console.log("Unrecognized message from " + SECOND_LAUNCH_WINDOW_MESSAGE_SOURCE + ": " + JSON.stringify(message)); + break; + } +} var WAIT_FOR_TOP_BAR_MS = 1000; function sendLocalStatusToQml() { @@ -397,6 +514,14 @@ function onMessageFromTopBar(message) { si.toggleStatus(); break; + case "displayInitialLaunchWindow": + displayInitialLaunchWindow(); + break; + + case "displaySecondLaunchWindow": + displaySecondLaunchWindow(); + break; + default: console.log("Unrecognized message from " + TOP_BAR_MESSAGE_SOURCE + ": " + JSON.stringify(message)); break; @@ -425,7 +550,6 @@ var TOP_BAR_QML_PATH = Script.resourcesPath() + "qml/hifi/simplifiedUI/topBar/Si var TOP_BAR_WINDOW_TITLE = "Simplified Top Bar"; var TOP_BAR_PRESENTATION_MODE = Desktop.PresentationMode.NATIVE; var TOP_BAR_WIDTH_PX = Window.innerWidth; -var TOP_BAR_HEIGHT_PX = 48; var topBarWindow = false; function loadSimplifiedTopBar() { var windowProps = { @@ -436,16 +560,9 @@ function loadSimplifiedTopBar() { y: TOP_BAR_HEIGHT_PX } }; - if (DOCKED_QML_SUPPORTED) { - windowProps.presentationWindowInfo = { - dockArea: Desktop.DockArea.TOP - }; - } else { - windowProps.position = { - x: Window.x, - y: Window.y - }; - } + windowProps.presentationWindowInfo = { + dockArea: Desktop.DockArea.TOP + }; topBarWindow = Desktop.createWindow(TOP_BAR_QML_PATH, windowProps); topBarWindow.fromQml.connect(onMessageFromTopBar); @@ -510,21 +627,38 @@ function onHMDInputDeviceMutedChanged(isMuted) { function onGeometryChanged(rect) { updateInputDeviceMutedOverlay(Audio.muted); updateOutputDeviceMutedOverlay(isOutputMuted()); - if (topBarWindow && !DOCKED_QML_SUPPORTED) { - topBarWindow.size = { - "x": rect.width, - "y": TOP_BAR_HEIGHT_PX - }; - topBarWindow.position = { - "x": rect.x, - "y": rect.y - }; +} + +var initialLaunchWindowIsMinimized = false; +var secondLaunchWindowIsMinimized = false; +function onWindowMinimizedChanged(isMinimized) { + if (isMinimized) { + handleInitialLaunchWindowVisibleChanged(false); + handleSecondLaunchWindowVisibleChanged(false); + } else if (!HMD.active) { + handleInitialLaunchWindowVisibleChanged(true); + handleSecondLaunchWindowVisibleChanged(true); } } -function onWindowMinimizedChanged() { - // prerequisite placeholder for Reduce Friction of Customer Acquisition sub task: https://highfidelity.atlassian.net/browse/DEV-585 - print("WINDOW MINIMIZED CHANGED SIGNAL"); +function handleInitialLaunchWindowVisibleChanged(shouldBeVisible) { + if (shouldBeVisible && !initialLaunchWindow && initialLaunchWindowIsMinimized) { + displayInitialLaunchWindow(); + initialLaunchWindowIsMinimized = false; + } else if (!shouldBeVisible && initialLaunchWindow) { + closeInitialLaunchWindow(); + initialLaunchWindowIsMinimized = true; + } +} + +function handleSecondLaunchWindowVisibleChanged(shouldBeVisible) { + if (shouldBeVisible && !secondLaunchWindow && secondLaunchWindowIsMinimized) { + displaySecondLaunchWindow(); + secondLaunchWindowIsMinimized = false; + } else if (!shouldBeVisible && secondLaunchWindow) { + closeSecondLaunchWindow(); + secondLaunchWindowIsMinimized = true; + } } function onDisplayModeChanged(isHMDMode) { @@ -534,8 +668,12 @@ function onDisplayModeChanged(isHMDMode) { if (isHMDMode) { onHMDInputDeviceMutedChanged(Audio.mutedHMD); + handleInitialLaunchWindowVisibleChanged(false); + handleSecondLaunchWindowVisibleChanged(false); } else { onDesktopInputDeviceMutedChanged(Audio.mutedDesktop); + handleInitialLaunchWindowVisibleChanged(true); + handleSecondLaunchWindowVisibleChanged(true); } } @@ -578,9 +716,9 @@ function restoreLODSettings() { } -var nametag = Script.require("./simplifiedNametag/simplifiedNametag.js?" + Date.now()); -var si = Script.require("./simplifiedStatusIndicator/simplifiedStatusIndicator.js?" + Date.now()); -var emote = Script.require("../simplifiedEmote/simplifiedEmote.js?" + Date.now()); +var nametag = Script.require("./simplifiedNametag/simplifiedNametag.js"); +var si = Script.require("./simplifiedStatusIndicator/simplifiedStatusIndicator.js"); +var simplifiedEmote = Script.require("../simplifiedEmote/simplifiedEmote.js"); var oldShowAudioTools; var oldShowBubbleTools; var keepExistingUIAndScriptsSetting = Settings.getValue("simplifiedUI/keepExistingUIAndScripts", false); @@ -647,6 +785,14 @@ function shutdown() { settingsAppWindow.close(); } + if (initialLaunchWindow) { + closeInitialLaunchWindow(); + } + + if (secondLaunchWindow) { + closeSecondLaunchWindow(); + } + maybeDeleteInputDeviceMutedOverlay(); maybeDeleteOutputDeviceMutedOverlay();