diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index f2301353b1..b21a27977e 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -152,6 +152,7 @@ #include "ui/LoginDialog.h" #include "ui/overlays/Cube3DOverlay.h" #include "ui/Snapshot.h" +#include "ui/SnapshotAnimated.h" #include "ui/StandAloneJSConsole.h" #include "ui/Stats.h" #include "ui/UpdateDialog.h" @@ -5428,19 +5429,9 @@ void Application::toggleLogDialog() { } } -// If the snapshot width or the framerate are too high for the -// application to handle, the framerate of the output GIF will drop. -#define SNAPSNOT_ANIMATED_WIDTH (720) -// This value should divide evenly into 100. Snapshot framerate is NOT guaranteed. -#define SNAPSNOT_ANIMATED_FRAMERATE_FPS (25) -#define SNAPSNOT_ANIMATED_DURATION_SECS (3) -#define SNAPSNOT_ANIMATED_FRAME_DELAY_MSEC (1000/SNAPSNOT_ANIMATED_FRAMERATE_FPS) -#define SNAPSNOT_ANIMATED_NUM_FRAMES (SNAPSNOT_ANIMATED_DURATION_SECS * SNAPSNOT_ANIMATED_FRAMERATE_FPS) - - -void Application::takeSnapshot(bool notify, float aspectRatio) { - postLambdaEvent([notify, aspectRatio, this] { +void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRatio) { + postLambdaEvent([notify, includeAnimated, aspectRatio, this] { QMediaPlayer* player = new QMediaPlayer(); QFileInfo inf = QFileInfo(PathUtils::resourcesPath() + "sounds/snap.wav"); player->setMedia(QUrl::fromLocalFile(inf.absoluteFilePath())); @@ -5449,87 +5440,7 @@ void Application::takeSnapshot(bool notify, float aspectRatio) { // Get a screenshot and save it QString path = Snapshot::saveSnapshot(getActiveDisplayPlugin()->getScreenshot(aspectRatio)); - // If we're in the middle of capturing a GIF... - if (_animatedSnapshotFirstFrameTimestamp != 0) - { - // Notify the window scripting interface that we've taken a Snapshot - emit DependencyManager::get()->snapshotTaken(path, notify); - // Protect against clobbering it and return immediately. - // (Perhaps with a "snapshot failed" message? - } - else - { - // Reset the current animated snapshot frame - qApp->_animatedSnapshotFirstFrameTimestamp = 0; - // Reset the current animated snapshot frame timestamp - qApp->_animatedSnapshotTimestamp = 0; - - // Change the extension of the future GIF saved snapshot file to "gif" - qApp->_animatedSnapshotPath = path.replace("jpg", "gif"); - - // Ensure the snapshot timer is Precise (attempted millisecond precision) - qApp->animatedSnapshotTimer.setTimerType(Qt::PreciseTimer); - - // Connect the animatedSnapshotTimer QTimer to the lambda slot function - connect(&(qApp->animatedSnapshotTimer), &QTimer::timeout, [=] { - // Get a screenshot from the display, then scale the screenshot down, - // then convert it to the image format the GIF library needs, - // then save all that to the QImage named "frame" - QImage frame(qApp->getActiveDisplayPlugin()->getScreenshot(aspectRatio)); - frame = frame.scaledToWidth(SNAPSNOT_ANIMATED_WIDTH); - frame = frame.convertToFormat(QImage::Format_RGBA8888); - - // If this is the first frame... - if (qApp->_animatedSnapshotTimestamp == 0) - { - // Write out the header and beginning of the GIF file - GifBegin(&(qApp->_animatedSnapshotGifWriter), qPrintable(qApp->_animatedSnapshotPath), frame.width(), frame.height(), SNAPSNOT_ANIMATED_FRAME_DELAY_MSEC / 10); - // Write the first to the gif - GifWriteFrame(&(qApp->_animatedSnapshotGifWriter), - (uint8_t*)frame.bits(), - frame.width(), - frame.height(), - SNAPSNOT_ANIMATED_FRAME_DELAY_MSEC / 10); - // Record the current frame timestamp - qApp->_animatedSnapshotTimestamp = QDateTime::currentMSecsSinceEpoch(); - qApp->_animatedSnapshotFirstFrameTimestamp = qApp->_animatedSnapshotTimestamp; - } - else - { - // If that was the last frame... - if ((qApp->_animatedSnapshotTimestamp - qApp->_animatedSnapshotFirstFrameTimestamp) >= (SNAPSNOT_ANIMATED_DURATION_SECS * 1000)) - { - // Reset the current frame timestamp - qApp->_animatedSnapshotTimestamp = 0; - qApp->_animatedSnapshotFirstFrameTimestamp = 0; - // Write out the end of the GIF - GifEnd(&(qApp->_animatedSnapshotGifWriter)); - // Notify the Window Scripting Interface that the snapshot was taken - emit DependencyManager::get()->snapshotTaken(qApp->_animatedSnapshotPath, false); - // Stop the snapshot QTimer - qApp->animatedSnapshotTimer.stop(); - } - else - { - // Variable used to determine how long the current frame took to pack - qint64 temp = QDateTime::currentMSecsSinceEpoch(); - // Write the frame to the gif - GifWriteFrame(&(qApp->_animatedSnapshotGifWriter), - (uint8_t*)frame.bits(), - frame.width(), - frame.height(), - round(((float)(QDateTime::currentMSecsSinceEpoch() - qApp->_animatedSnapshotTimestamp + qApp->_animatedSnapshotLastWriteFrameDuration)) / 10)); - // Record how long it took for the current frame to pack - qApp->_animatedSnapshotLastWriteFrameDuration = QDateTime::currentMSecsSinceEpoch() - temp; - // Record the current frame timestamp - qApp->_animatedSnapshotTimestamp = QDateTime::currentMSecsSinceEpoch(); - } - } - }); - - // Start the animatedSnapshotTimer QTimer - argument for this is in milliseconds - qApp->animatedSnapshotTimer.start(SNAPSNOT_ANIMATED_FRAME_DELAY_MSEC); - } + SnapshotAnimated::saveSnapshotAnimated(includeAnimated, path, aspectRatio, qApp, DependencyManager::get()); }); } void Application::shareSnapshot(const QString& path) { diff --git a/interface/src/Application.h b/interface/src/Application.h index 49ad9ec03b..5397420497 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -267,7 +267,7 @@ public: float getAvatarSimrate() const { return _avatarSimCounter.rate(); } float getAverageSimsPerSecond() const { return _simCounter.rate(); } - void takeSnapshot(bool notify, float aspectRatio = 0.0f); + void takeSnapshot(bool notify, bool includeAnimated = false, float aspectRatio = 0.0f); void shareSnapshot(const QString& filename); model::SkyboxPointer getDefaultSkybox() const { return _defaultSkybox; } @@ -610,13 +610,6 @@ private: model::SkyboxPointer _defaultSkybox { new ProceduralSkybox() } ; gpu::TexturePointer _defaultSkyboxTexture; gpu::TexturePointer _defaultSkyboxAmbientTexture; - - QTimer animatedSnapshotTimer; - GifWriter _animatedSnapshotGifWriter; - qint64 _animatedSnapshotTimestamp { 0 }; - qint64 _animatedSnapshotFirstFrameTimestamp { 0 }; - qint64 _animatedSnapshotLastWriteFrameDuration { 20 }; - QString _animatedSnapshotPath; }; diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index 0f9dd698fd..0cb574c1f6 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -199,8 +199,8 @@ void WindowScriptingInterface::copyToClipboard(const QString& text) { QApplication::clipboard()->setText(text); } -void WindowScriptingInterface::takeSnapshot(bool notify, float aspectRatio) { - qApp->takeSnapshot(notify, aspectRatio); +void WindowScriptingInterface::takeSnapshot(bool notify, bool includeAnimated, float aspectRatio) { + qApp->takeSnapshot(notify, includeAnimated, aspectRatio); } void WindowScriptingInterface::shareSnapshot(const QString& path) { diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index f4a89ae221..7246dc0927 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -52,7 +52,7 @@ public slots: QScriptValue save(const QString& title = "", const QString& directory = "", const QString& nameFilter = ""); void showAssetServer(const QString& upload = ""); void copyToClipboard(const QString& text); - void takeSnapshot(bool notify = true, float aspectRatio = 0.0f); + void takeSnapshot(bool notify = true, bool includeAnimated = false, float aspectRatio = 0.0f); void shareSnapshot(const QString& path); bool isPhysicsEnabled(); @@ -60,7 +60,7 @@ signals: void domainChanged(const QString& domainHostname); void svoImportRequested(const QString& url); void domainConnectionRefused(const QString& reasonMessage, int reasonCode, const QString& extraInfo); - void snapshotTaken(const QString& path, bool notify); + void snapshotTaken(const QString& pathStillSnapshot, const QString& pathAnimatedSnapshot, bool notify); void snapshotShared(const QString& error); private: diff --git a/interface/src/ui/SnapshotAnimated.cpp b/interface/src/ui/SnapshotAnimated.cpp new file mode 100644 index 0000000000..a9c6394426 --- /dev/null +++ b/interface/src/ui/SnapshotAnimated.cpp @@ -0,0 +1,105 @@ +// +// SnapshotAnimated.cpp +// interface/src/ui +// +// Created by Zach Fox on 11/14/16. +// 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 +// + +#include +#include +#include +#include + +#include "SnapshotAnimated.h" + +QTimer SnapshotAnimated::snapshotAnimatedTimer; +GifWriter SnapshotAnimated::snapshotAnimatedGifWriter; +qint64 SnapshotAnimated::snapshotAnimatedTimestamp = 0; +qint64 SnapshotAnimated::snapshotAnimatedFirstFrameTimestamp = 0; +qint64 SnapshotAnimated::snapshotAnimatedLastWriteFrameDuration = 0; + +void SnapshotAnimated::saveSnapshotAnimated(bool includeAnimated, QString pathStillSnapshot, float aspectRatio, Application* app, QSharedPointer dm) { + // If we're not in the middle of capturing an animated snapshot... + if ((snapshotAnimatedFirstFrameTimestamp == 0) && (includeAnimated)) + { + // Define the output location of the animated snapshot + QString pathAnimatedSnapshot(pathStillSnapshot); + pathAnimatedSnapshot.replace("jpg", "gif"); + // Reset the current animated snapshot last frame duration + snapshotAnimatedLastWriteFrameDuration = SNAPSNOT_ANIMATED_INITIAL_WRITE_DURATION_MSEC; + + // Ensure the snapshot timer is Precise (attempted millisecond precision) + snapshotAnimatedTimer.setTimerType(Qt::PreciseTimer); + + // Connect the snapshotAnimatedTimer QTimer to the lambda slot function + QObject::connect(&(snapshotAnimatedTimer), &QTimer::timeout, [=] { + // Get a screenshot from the display, then scale the screenshot down, + // then convert it to the image format the GIF library needs, + // then save all that to the QImage named "frame" + QImage frame(app->getActiveDisplayPlugin()->getScreenshot(aspectRatio)); + frame = frame.scaledToWidth(SNAPSNOT_ANIMATED_WIDTH); + frame = frame.convertToFormat(QImage::Format_RGBA8888); + + // If this is the first frame... + if (snapshotAnimatedTimestamp == 0) + { + // Write out the header and beginning of the GIF file + GifBegin(&(snapshotAnimatedGifWriter), qPrintable(pathAnimatedSnapshot), frame.width(), frame.height(), SNAPSNOT_ANIMATED_FRAME_DELAY_MSEC / 10); + // Write the first to the gif + GifWriteFrame(&(snapshotAnimatedGifWriter), + (uint8_t*)frame.bits(), + frame.width(), + frame.height(), + SNAPSNOT_ANIMATED_FRAME_DELAY_MSEC / 10); + // Record the current frame timestamp + snapshotAnimatedTimestamp = QDateTime::currentMSecsSinceEpoch(); + snapshotAnimatedFirstFrameTimestamp = snapshotAnimatedTimestamp; + } + else + { + // If that was the last frame... + if ((snapshotAnimatedTimestamp - snapshotAnimatedFirstFrameTimestamp) >= (SNAPSNOT_ANIMATED_DURATION_SECS * 1000)) + { + // Reset the current frame timestamp + snapshotAnimatedTimestamp = 0; + snapshotAnimatedFirstFrameTimestamp = 0; + // Write out the end of the GIF + GifEnd(&(snapshotAnimatedGifWriter)); + // Stop the snapshot QTimer + snapshotAnimatedTimer.stop(); + emit dm->snapshotTaken(pathStillSnapshot, pathAnimatedSnapshot, false); + qDebug() << "still: " << pathStillSnapshot << "anim: " << pathAnimatedSnapshot; + //emit dm->snapshotTaken("C:\\Users\\Zach Fox\\Desktop\\hifi-snap-by-zfox-on-2016-11-14_17-07-33.jpg", "C:\\Users\\Zach Fox\\Desktop\\hifi-snap-by-zfox-on-2016-11-14_17-10-02.gif", false); + } + else + { + // Variable used to determine how long the current frame took to pack + qint64 framePackStartTime = QDateTime::currentMSecsSinceEpoch(); + // Write the frame to the gif + GifWriteFrame(&(snapshotAnimatedGifWriter), + (uint8_t*)frame.bits(), + frame.width(), + frame.height(), + round(((float)(QDateTime::currentMSecsSinceEpoch() - snapshotAnimatedTimestamp + snapshotAnimatedLastWriteFrameDuration)) / 10)); + // Record how long it took for the current frame to pack + snapshotAnimatedLastWriteFrameDuration = QDateTime::currentMSecsSinceEpoch() - framePackStartTime; + // Record the current frame timestamp + snapshotAnimatedTimestamp = QDateTime::currentMSecsSinceEpoch(); + } + } + }); + + // Start the snapshotAnimatedTimer QTimer - argument for this is in milliseconds + snapshotAnimatedTimer.start(SNAPSNOT_ANIMATED_FRAME_DELAY_MSEC); + } + // If we're already in the middle of capturing an animated snapshot... + else + { + // Just tell the dependency manager that the capture of the still snapshot has taken place. + emit dm->snapshotTaken(pathStillSnapshot, "", false); + } +} diff --git a/interface/src/ui/SnapshotAnimated.h b/interface/src/ui/SnapshotAnimated.h new file mode 100644 index 0000000000..ca778341a6 --- /dev/null +++ b/interface/src/ui/SnapshotAnimated.h @@ -0,0 +1,44 @@ +// +// SnapshotAnimated.h +// interface/src/ui +// +// Created by Zach Fox on 11/14/16. +// 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 +// + +#ifndef hifi_SnapshotAnimated_h +#define hifi_SnapshotAnimated_h + +#include +#include +#include +#include +#include "scripting/WindowScriptingInterface.h" + +// If the snapshot width or the framerate are too high for the +// application to handle, the framerate of the output GIF will drop. +#define SNAPSNOT_ANIMATED_WIDTH (720) +// This value should divide evenly into 100. Snapshot framerate is NOT guaranteed. +#define SNAPSNOT_ANIMATED_TARGET_FRAMERATE (25) +#define SNAPSNOT_ANIMATED_DURATION_SECS (3) + +#define SNAPSNOT_ANIMATED_FRAME_DELAY_MSEC (1000/SNAPSNOT_ANIMATED_TARGET_FRAMERATE) +// This is the fudge factor that we add to the *first* GIF frame's "delay" value +#define SNAPSNOT_ANIMATED_INITIAL_WRITE_DURATION_MSEC (20) +#define SNAPSNOT_ANIMATED_NUM_FRAMES (SNAPSNOT_ANIMATED_DURATION_SECS * SNAPSNOT_ANIMATED_TARGET_FRAMERATE) + +class SnapshotAnimated { +private: + static QTimer snapshotAnimatedTimer; + static GifWriter snapshotAnimatedGifWriter; + static qint64 snapshotAnimatedTimestamp; + static qint64 snapshotAnimatedFirstFrameTimestamp; + static qint64 snapshotAnimatedLastWriteFrameDuration; +public: + static void saveSnapshotAnimated(bool includeAnimated, QString stillSnapshotPath, float aspectRatio, Application* app, QSharedPointer dm); +}; + +#endif // hifi_SnapshotAnimated_h diff --git a/scripts/system/html/SnapshotReview.html b/scripts/system/html/SnapshotReview.html index db70a1910b..d37afb180c 100644 --- a/scripts/system/html/SnapshotReview.html +++ b/scripts/system/html/SnapshotReview.html @@ -1,48 +1,48 @@ - + Share - + - +
-
-
- -
-
-
-
-
Would you like to share your pic in the Snapshots feed?
-
- - - - +
+
+ +
+
+
+
+
Would you like to share your pics in the Snapshots feed?
+
+ + + + +
+
+
+ +
+
+
+
+ + + + + + + +
-
-
- -
-
-
- - - - - - - - +
-
-
-
- + diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index a6515df825..ccd70c40b2 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -12,29 +12,30 @@ var paths = [], idCounter = 0, useCheckboxes; function addImage(data) { - var div = document.createElement("DIV"), - input = document.createElement("INPUT"), - label = document.createElement("LABEL"), - img = document.createElement("IMG"), - id = "p" + idCounter++; - function toggle() { data.share = input.checked; } - img.src = data.localPath; - div.appendChild(img); - data.share = true; - if (useCheckboxes) { // I'd rather use css, but the included stylesheet is quite particular. - // Our stylesheet(?) requires input.id to match label.for. Otherwise input doesn't display the check state. - label.setAttribute('for', id); // cannot do label.for = - input.id = id; - input.type = "checkbox"; - input.checked = true; - input.addEventListener('change', toggle); - div.class = "property checkbox"; - div.appendChild(input); - div.appendChild(label); + if (data.localPath) { + var div = document.createElement("DIV"), + input = document.createElement("INPUT"), + label = document.createElement("LABEL"), + img = document.createElement("IMG"), + id = "p" + idCounter++; + function toggle() { data.share = input.checked; } + img.src = data.localPath; + div.appendChild(img); + data.share = true; + if (useCheckboxes) { // I'd rather use css, but the included stylesheet is quite particular. + // Our stylesheet(?) requires input.id to match label.for. Otherwise input doesn't display the check state. + label.setAttribute('for', id); // cannot do label.for = + input.id = id; + input.type = "checkbox"; + input.checked = (id === "p0"); + input.addEventListener('change', toggle); + div.class = "property checkbox"; + div.appendChild(input); + div.appendChild(label); + } + document.getElementById("snapshot-images").appendChild(div); + paths.push(data); } - document.getElementById("snapshot-images").appendChild(div); - paths.push(data); - } function handleShareButtons(shareMsg) { var openFeed = document.getElementById('openFeed'); @@ -49,7 +50,7 @@ function handleShareButtons(shareMsg) { window.onload = function () { // Something like the following will allow testing in a browser. //addImage({localPath: 'c:/Users/howar/OneDrive/Pictures/hifi-snap-by--on-2016-07-27_12-58-43.jpg'}); - //addImage({localPath: 'http://lorempixel.com/1512/1680'}); + //addImage({ localPath: 'http://lorempixel.com/1512/1680' }); openEventBridge(function () { // Set up a handler for receiving the data, and tell the .js we are ready to receive it. EventBridge.scriptEventReceived.connect(function (message) { diff --git a/scripts/system/notifications.js b/scripts/system/notifications.js index d89b532f31..4c16a637bf 100644 --- a/scripts/system/notifications.js +++ b/scripts/system/notifications.js @@ -58,581 +58,583 @@ /* global Script, Controller, Overlays, SoundArray, Quat, Vec3, MyAvatar, Menu, HMD, AudioDevice, LODManager, Settings, Camera */ -(function() { // BEGIN LOCAL_SCOPE +(function () { // BEGIN LOCAL_SCOPE -Script.include("./libraries/soundArray.js"); + Script.include("./libraries/soundArray.js"); -var width = 340.0; //width of notification overlay -var windowDimensions = Controller.getViewportDimensions(); // get the size of the interface window -var overlayLocationX = (windowDimensions.x - (width + 20.0)); // positions window 20px from the right of the interface window -var buttonLocationX = overlayLocationX + (width - 28.0); -var locationY = 20.0; // position down from top of interface window -var topMargin = 13.0; -var leftMargin = 10.0; -var textColor = { red: 228, green: 228, blue: 228}; // text color -var backColor = { red: 2, green: 2, blue: 2}; // background color was 38,38,38 -var backgroundAlpha = 0; -var fontSize = 12.0; -var PERSIST_TIME_2D = 10.0; // Time in seconds before notification fades -var PERSIST_TIME_3D = 15.0; -var persistTime = PERSIST_TIME_2D; -var frame = 0; -var ourWidth = Window.innerWidth; -var ourHeight = Window.innerHeight; -var ctrlIsPressed = false; -var ready = true; -var MENU_NAME = 'Tools > Notifications'; -var PLAY_NOTIFICATION_SOUNDS_MENU_ITEM = "Play Notification Sounds"; -var NOTIFICATION_MENU_ITEM_POST = " Notifications"; -var PLAY_NOTIFICATION_SOUNDS_SETTING = "play_notification_sounds"; -var PLAY_NOTIFICATION_SOUNDS_TYPE_SETTING_PRE = "play_notification_sounds_type_"; -var lodTextID = false; + var width = 340.0; //width of notification overlay + var windowDimensions = Controller.getViewportDimensions(); // get the size of the interface window + var overlayLocationX = (windowDimensions.x - (width + 20.0)); // positions window 20px from the right of the interface window + var buttonLocationX = overlayLocationX + (width - 28.0); + var locationY = 20.0; // position down from top of interface window + var topMargin = 13.0; + var leftMargin = 10.0; + var textColor = { red: 228, green: 228, blue: 228 }; // text color + var backColor = { red: 2, green: 2, blue: 2 }; // background color was 38,38,38 + var backgroundAlpha = 0; + var fontSize = 12.0; + var PERSIST_TIME_2D = 10.0; // Time in seconds before notification fades + var PERSIST_TIME_3D = 15.0; + var persistTime = PERSIST_TIME_2D; + var frame = 0; + var ourWidth = Window.innerWidth; + var ourHeight = Window.innerHeight; + var ctrlIsPressed = false; + var ready = true; + var MENU_NAME = 'Tools > Notifications'; + var PLAY_NOTIFICATION_SOUNDS_MENU_ITEM = "Play Notification Sounds"; + var NOTIFICATION_MENU_ITEM_POST = " Notifications"; + var PLAY_NOTIFICATION_SOUNDS_SETTING = "play_notification_sounds"; + var PLAY_NOTIFICATION_SOUNDS_TYPE_SETTING_PRE = "play_notification_sounds_type_"; + var lodTextID = false; -var NotificationType = { - UNKNOWN: 0, - SNAPSHOT: 1, - LOD_WARNING: 2, - CONNECTION_REFUSED: 3, - EDIT_ERROR: 4, - properties: [ - { text: "Snapshot" }, - { text: "Level of Detail" }, - { text: "Connection Refused" }, - { text: "Edit error" } - ], - getTypeFromMenuItem: function(menuItemName) { - if (menuItemName.substr(menuItemName.length - NOTIFICATION_MENU_ITEM_POST.length) !== NOTIFICATION_MENU_ITEM_POST) { - return NotificationType.UNKNOWN; - } - var preMenuItemName = menuItemName.substr(0, menuItemName.length - NOTIFICATION_MENU_ITEM_POST.length); - for (var type in this.properties) { - if (this.properties[type].text === preMenuItemName) { - return parseInt(type) + 1; + var NotificationType = { + UNKNOWN: 0, + SNAPSHOT: 1, + LOD_WARNING: 2, + CONNECTION_REFUSED: 3, + EDIT_ERROR: 4, + properties: [ + { text: "Snapshot" }, + { text: "Level of Detail" }, + { text: "Connection Refused" }, + { text: "Edit error" } + ], + getTypeFromMenuItem: function (menuItemName) { + if (menuItemName.substr(menuItemName.length - NOTIFICATION_MENU_ITEM_POST.length) !== NOTIFICATION_MENU_ITEM_POST) { + return NotificationType.UNKNOWN; } + var preMenuItemName = menuItemName.substr(0, menuItemName.length - NOTIFICATION_MENU_ITEM_POST.length); + for (var type in this.properties) { + if (this.properties[type].text === preMenuItemName) { + return parseInt(type) + 1; + } + } + return NotificationType.UNKNOWN; + }, + getMenuString: function (type) { + return this.properties[type - 1].text + NOTIFICATION_MENU_ITEM_POST; } - return NotificationType.UNKNOWN; - }, - getMenuString: function(type) { - return this.properties[type - 1].text + NOTIFICATION_MENU_ITEM_POST; - } -}; - -var randomSounds = new SoundArray({ localOnly: true }, true); -var numberOfSounds = 2; -for (var i = 1; i <= numberOfSounds; i++) { - randomSounds.addSound(Script.resolvePath("assets/sounds/notification-general"+ i + ".raw")); -} - -var notifications = []; -var buttons = []; -var times = []; -var heights = []; -var myAlpha = []; -var arrays = []; -var isOnHMD = false, - NOTIFICATIONS_3D_DIRECTION = 0.0, // Degrees from avatar orientation. - NOTIFICATIONS_3D_DISTANCE = 0.6, // Horizontal distance from avatar position. - NOTIFICATIONS_3D_ELEVATION = -0.8, // Height of top middle of top notification relative to avatar eyes. - NOTIFICATIONS_3D_YAW = 0.0, // Degrees relative to notifications direction. - NOTIFICATIONS_3D_PITCH = -60.0, // Degrees from vertical. - NOTIFICATION_3D_SCALE = 0.002, // Multiplier that converts 2D overlay dimensions to 3D overlay dimensions. - NOTIFICATION_3D_BUTTON_WIDTH = 40 * NOTIFICATION_3D_SCALE, // Need a little more room for button in 3D. - overlay3DDetails = []; - -// push data from above to the 2 dimensional array -function createArrays(notice, button, createTime, height, myAlpha) { - arrays.push([notice, button, createTime, height, myAlpha]); -} - -// This handles the final dismissal of a notification after fading -function dismiss(firstNoteOut, firstButOut, firstOut) { - if (firstNoteOut == lodTextID) { - lodTextID = false; - } - - Overlays.deleteOverlay(firstNoteOut); - Overlays.deleteOverlay(firstButOut); - notifications.splice(firstOut, 1); - buttons.splice(firstOut, 1); - times.splice(firstOut, 1); - heights.splice(firstOut, 1); - myAlpha.splice(firstOut, 1); - overlay3DDetails.splice(firstOut, 1); -} - -function fadeIn(noticeIn, buttonIn) { - var q = 0, - qFade, - pauseTimer = null; - - pauseTimer = Script.setInterval(function () { - q += 1; - qFade = q / 10.0; - Overlays.editOverlay(noticeIn, { alpha: qFade }); - Overlays.editOverlay(buttonIn, { alpha: qFade }); - if (q >= 9.0) { - Script.clearInterval(pauseTimer); - } - }, 10); -} - -// this fades the notification ready for dismissal, and removes it from the arrays -function fadeOut(noticeOut, buttonOut, arraysOut) { - var r = 9.0, - rFade, - pauseTimer = null; - - pauseTimer = Script.setInterval(function () { - r -= 1; - rFade = r / 10.0; - Overlays.editOverlay(noticeOut, { alpha: rFade }); - Overlays.editOverlay(buttonOut, { alpha: rFade }); - if (r < 0) { - dismiss(noticeOut, buttonOut, arraysOut); - arrays.splice(arraysOut, 1); - ready = true; - Script.clearInterval(pauseTimer); - } - }, 20); -} - -function calculate3DOverlayPositions(noticeWidth, noticeHeight, y) { - // Calculates overlay positions and orientations in avatar coordinates. - var noticeY, - originOffset, - notificationOrientation, - notificationPosition, - buttonPosition; - - // Notification plane positions - noticeY = -y * NOTIFICATION_3D_SCALE - noticeHeight / 2; - notificationPosition = { x: 0, y: noticeY, z: 0 }; - buttonPosition = { x: (noticeWidth - NOTIFICATION_3D_BUTTON_WIDTH) / 2, y: noticeY, z: 0.001 }; - - // Rotate plane - notificationOrientation = Quat.fromPitchYawRollDegrees(NOTIFICATIONS_3D_PITCH, - NOTIFICATIONS_3D_DIRECTION + NOTIFICATIONS_3D_YAW, 0); - notificationPosition = Vec3.multiplyQbyV(notificationOrientation, notificationPosition); - buttonPosition = Vec3.multiplyQbyV(notificationOrientation, buttonPosition); - - // Translate plane - originOffset = Vec3.multiplyQbyV(Quat.fromPitchYawRollDegrees(0, NOTIFICATIONS_3D_DIRECTION, 0), - { x: 0, y: 0, z: -NOTIFICATIONS_3D_DISTANCE }); - originOffset.y += NOTIFICATIONS_3D_ELEVATION; - notificationPosition = Vec3.sum(originOffset, notificationPosition); - buttonPosition = Vec3.sum(originOffset, buttonPosition); - - return { - notificationOrientation: notificationOrientation, - notificationPosition: notificationPosition, - buttonPosition: buttonPosition }; -} -// Pushes data to each array and sets up data for 2nd dimension array -// to handle auxiliary data not carried by the overlay class -// specifically notification "heights", "times" of creation, and . -function notify(notice, button, height, imageProperties, image) { - var notificationText, - noticeWidth, - noticeHeight, - positions, - last; + var randomSounds = new SoundArray({ localOnly: true }, true); + var numberOfSounds = 2; + for (var i = 1; i <= numberOfSounds; i++) { + randomSounds.addSound(Script.resolvePath("assets/sounds/notification-general" + i + ".raw")); + } - if (isOnHMD) { - // Calculate 3D values from 2D overlay properties. + var notifications = []; + var buttons = []; + var times = []; + var heights = []; + var myAlpha = []; + var arrays = []; + var isOnHMD = false, + NOTIFICATIONS_3D_DIRECTION = 0.0, // Degrees from avatar orientation. + NOTIFICATIONS_3D_DISTANCE = 0.6, // Horizontal distance from avatar position. + NOTIFICATIONS_3D_ELEVATION = -0.8, // Height of top middle of top notification relative to avatar eyes. + NOTIFICATIONS_3D_YAW = 0.0, // Degrees relative to notifications direction. + NOTIFICATIONS_3D_PITCH = -60.0, // Degrees from vertical. + NOTIFICATION_3D_SCALE = 0.002, // Multiplier that converts 2D overlay dimensions to 3D overlay dimensions. + NOTIFICATION_3D_BUTTON_WIDTH = 40 * NOTIFICATION_3D_SCALE, // Need a little more room for button in 3D. + overlay3DDetails = []; - noticeWidth = notice.width * NOTIFICATION_3D_SCALE + NOTIFICATION_3D_BUTTON_WIDTH; - noticeHeight = notice.height * NOTIFICATION_3D_SCALE; + // push data from above to the 2 dimensional array + function createArrays(notice, button, createTime, height, myAlpha) { + arrays.push([notice, button, createTime, height, myAlpha]); + } - notice.size = { x: noticeWidth, y: noticeHeight }; - - positions = calculate3DOverlayPositions(noticeWidth, noticeHeight, notice.y); - - notice.parentID = MyAvatar.sessionUUID; - notice.parentJointIndex = -2; - - if (!image) { - notice.topMargin = 0.75 * notice.topMargin * NOTIFICATION_3D_SCALE; - notice.leftMargin = 2 * notice.leftMargin * NOTIFICATION_3D_SCALE; - notice.bottomMargin = 0; - notice.rightMargin = 0; - notice.lineHeight = 10.0 * (fontSize / 12.0) * NOTIFICATION_3D_SCALE; - notice.isFacingAvatar = false; - - notificationText = Overlays.addOverlay("text3d", notice); - notifications.push(notificationText); - } else { - notifications.push(Overlays.addOverlay("image3d", notice)); + // This handles the final dismissal of a notification after fading + function dismiss(firstNoteOut, firstButOut, firstOut) { + if (firstNoteOut == lodTextID) { + lodTextID = false; } - button.url = button.imageURL; - button.scale = button.width * NOTIFICATION_3D_SCALE; - button.isFacingAvatar = false; - button.parentID = MyAvatar.sessionUUID; - button.parentJointIndex = -2; + Overlays.deleteOverlay(firstNoteOut); + Overlays.deleteOverlay(firstButOut); + notifications.splice(firstOut, 1); + buttons.splice(firstOut, 1); + times.splice(firstOut, 1); + heights.splice(firstOut, 1); + myAlpha.splice(firstOut, 1); + overlay3DDetails.splice(firstOut, 1); + } - buttons.push((Overlays.addOverlay("image3d", button))); - overlay3DDetails.push({ - notificationOrientation: positions.notificationOrientation, - notificationPosition: positions.notificationPosition, - buttonPosition: positions.buttonPosition, - width: noticeWidth, - height: noticeHeight - }); + function fadeIn(noticeIn, buttonIn) { + var q = 0, + qFade, + pauseTimer = null; + pauseTimer = Script.setInterval(function () { + q += 1; + qFade = q / 10.0; + Overlays.editOverlay(noticeIn, { alpha: qFade }); + Overlays.editOverlay(buttonIn, { alpha: qFade }); + if (q >= 9.0) { + Script.clearInterval(pauseTimer); + } + }, 10); + } - var defaultEyePosition, - avatarOrientation, - notificationPosition, + // this fades the notification ready for dismissal, and removes it from the arrays + function fadeOut(noticeOut, buttonOut, arraysOut) { + var r = 9.0, + rFade, + pauseTimer = null; + + pauseTimer = Script.setInterval(function () { + r -= 1; + rFade = r / 10.0; + Overlays.editOverlay(noticeOut, { alpha: rFade }); + Overlays.editOverlay(buttonOut, { alpha: rFade }); + if (r < 0) { + dismiss(noticeOut, buttonOut, arraysOut); + arrays.splice(arraysOut, 1); + ready = true; + Script.clearInterval(pauseTimer); + } + }, 20); + } + + function calculate3DOverlayPositions(noticeWidth, noticeHeight, y) { + // Calculates overlay positions and orientations in avatar coordinates. + var noticeY, + originOffset, notificationOrientation, + notificationPosition, buttonPosition; - if (isOnHMD && notifications.length > 0) { - // Update 3D overlays to maintain positions relative to avatar - defaultEyePosition = MyAvatar.getDefaultEyePosition(); - avatarOrientation = MyAvatar.orientation; + // Notification plane positions + noticeY = -y * NOTIFICATION_3D_SCALE - noticeHeight / 2; + notificationPosition = { x: 0, y: noticeY, z: 0 }; + buttonPosition = { x: (noticeWidth - NOTIFICATION_3D_BUTTON_WIDTH) / 2, y: noticeY, z: 0.001 }; - for (i = 0; i < notifications.length; i += 1) { - notificationPosition = Vec3.sum(defaultEyePosition, - Vec3.multiplyQbyV(avatarOrientation, - overlay3DDetails[i].notificationPosition)); - notificationOrientation = Quat.multiply(avatarOrientation, - overlay3DDetails[i].notificationOrientation); - buttonPosition = Vec3.sum(defaultEyePosition, - Vec3.multiplyQbyV(avatarOrientation, - overlay3DDetails[i].buttonPosition)); - Overlays.editOverlay(notifications[i], { position: notificationPosition, - rotation: notificationOrientation }); - Overlays.editOverlay(buttons[i], { position: buttonPosition, rotation: notificationOrientation }); - } - } + // Rotate plane + notificationOrientation = Quat.fromPitchYawRollDegrees(NOTIFICATIONS_3D_PITCH, + NOTIFICATIONS_3D_DIRECTION + NOTIFICATIONS_3D_YAW, 0); + notificationPosition = Vec3.multiplyQbyV(notificationOrientation, notificationPosition); + buttonPosition = Vec3.multiplyQbyV(notificationOrientation, buttonPosition); - } else { - if (!image) { - notificationText = Overlays.addOverlay("text", notice); - notifications.push((notificationText)); - } else { - notifications.push(Overlays.addOverlay("image", notice)); - } - buttons.push(Overlays.addOverlay("image", button)); + // Translate plane + originOffset = Vec3.multiplyQbyV(Quat.fromPitchYawRollDegrees(0, NOTIFICATIONS_3D_DIRECTION, 0), + { x: 0, y: 0, z: -NOTIFICATIONS_3D_DISTANCE }); + originOffset.y += NOTIFICATIONS_3D_ELEVATION; + notificationPosition = Vec3.sum(originOffset, notificationPosition); + buttonPosition = Vec3.sum(originOffset, buttonPosition); + + return { + notificationOrientation: notificationOrientation, + notificationPosition: notificationPosition, + buttonPosition: buttonPosition + }; } - height = height + 1.0; - heights.push(height); - times.push(new Date().getTime() / 1000); - last = notifications.length - 1; - myAlpha.push(notifications[last].alpha); - createArrays(notifications[last], buttons[last], times[last], heights[last], myAlpha[last]); - fadeIn(notifications[last], buttons[last]); + // Pushes data to each array and sets up data for 2nd dimension array + // to handle auxiliary data not carried by the overlay class + // specifically notification "heights", "times" of creation, and . + function notify(notice, button, height, imageProperties, image) { + var notificationText, + noticeWidth, + noticeHeight, + positions, + last; - if (imageProperties && !image) { - var imageHeight = notice.width / imageProperties.aspectRatio; - notice = { - x: notice.x, - y: notice.y + height, - width: notice.width, - height: imageHeight, - subImage: { x: 0, y: 0 }, - color: { red: 255, green: 255, blue: 255}, + if (isOnHMD) { + // Calculate 3D values from 2D overlay properties. + + noticeWidth = notice.width * NOTIFICATION_3D_SCALE + NOTIFICATION_3D_BUTTON_WIDTH; + noticeHeight = notice.height * NOTIFICATION_3D_SCALE; + + notice.size = { x: noticeWidth, y: noticeHeight }; + + positions = calculate3DOverlayPositions(noticeWidth, noticeHeight, notice.y); + + notice.parentID = MyAvatar.sessionUUID; + notice.parentJointIndex = -2; + + if (!image) { + notice.topMargin = 0.75 * notice.topMargin * NOTIFICATION_3D_SCALE; + notice.leftMargin = 2 * notice.leftMargin * NOTIFICATION_3D_SCALE; + notice.bottomMargin = 0; + notice.rightMargin = 0; + notice.lineHeight = 10.0 * (fontSize / 12.0) * NOTIFICATION_3D_SCALE; + notice.isFacingAvatar = false; + + notificationText = Overlays.addOverlay("text3d", notice); + notifications.push(notificationText); + } else { + notifications.push(Overlays.addOverlay("image3d", notice)); + } + + button.url = button.imageURL; + button.scale = button.width * NOTIFICATION_3D_SCALE; + button.isFacingAvatar = false; + button.parentID = MyAvatar.sessionUUID; + button.parentJointIndex = -2; + + buttons.push((Overlays.addOverlay("image3d", button))); + overlay3DDetails.push({ + notificationOrientation: positions.notificationOrientation, + notificationPosition: positions.notificationPosition, + buttonPosition: positions.buttonPosition, + width: noticeWidth, + height: noticeHeight + }); + + + var defaultEyePosition, + avatarOrientation, + notificationPosition, + notificationOrientation, + buttonPosition; + + if (isOnHMD && notifications.length > 0) { + // Update 3D overlays to maintain positions relative to avatar + defaultEyePosition = MyAvatar.getDefaultEyePosition(); + avatarOrientation = MyAvatar.orientation; + + for (i = 0; i < notifications.length; i += 1) { + notificationPosition = Vec3.sum(defaultEyePosition, + Vec3.multiplyQbyV(avatarOrientation, + overlay3DDetails[i].notificationPosition)); + notificationOrientation = Quat.multiply(avatarOrientation, + overlay3DDetails[i].notificationOrientation); + buttonPosition = Vec3.sum(defaultEyePosition, + Vec3.multiplyQbyV(avatarOrientation, + overlay3DDetails[i].buttonPosition)); + Overlays.editOverlay(notifications[i], { + position: notificationPosition, + rotation: notificationOrientation + }); + Overlays.editOverlay(buttons[i], { position: buttonPosition, rotation: notificationOrientation }); + } + } + + } else { + if (!image) { + notificationText = Overlays.addOverlay("text", notice); + notifications.push((notificationText)); + } else { + notifications.push(Overlays.addOverlay("image", notice)); + } + buttons.push(Overlays.addOverlay("image", button)); + } + + height = height + 1.0; + heights.push(height); + times.push(new Date().getTime() / 1000); + last = notifications.length - 1; + myAlpha.push(notifications[last].alpha); + createArrays(notifications[last], buttons[last], times[last], heights[last], myAlpha[last]); + fadeIn(notifications[last], buttons[last]); + + if (imageProperties && !image) { + var imageHeight = notice.width / imageProperties.aspectRatio; + notice = { + x: notice.x, + y: notice.y + height, + width: notice.width, + height: imageHeight, + subImage: { x: 0, y: 0 }, + color: { red: 255, green: 255, blue: 255 }, + visible: true, + imageURL: imageProperties.path, + alpha: backgroundAlpha + }; + notify(notice, button, imageHeight, imageProperties, true); + } + + return notificationText; + } + + var CLOSE_NOTIFICATION_ICON = Script.resolvePath("assets/images/close-small-light.svg"); + + // This function creates and sizes the overlays + function createNotification(text, notificationType, imageProperties) { + var count = (text.match(/\n/g) || []).length, + breakPoint = 43.0, // length when new line is added + extraLine = 0, + breaks = 0, + height = 40.0, + stack = 0, + level, + noticeProperties, + bLevel, + buttonProperties, + i; + + if (text.length >= breakPoint) { + breaks = count; + } + extraLine = breaks * 16.0; + for (i = 0; i < heights.length; i += 1) { + stack = stack + heights[i]; + } + + level = (stack + 20.0); + height = height + extraLine; + + noticeProperties = { + x: overlayLocationX, + y: level, + width: width, + height: height, + color: textColor, + backgroundColor: backColor, + alpha: backgroundAlpha, + topMargin: topMargin, + leftMargin: leftMargin, + font: { size: fontSize }, + text: text + }; + + bLevel = level + 12.0; + buttonProperties = { + x: buttonLocationX, + y: bLevel, + width: 10.0, + height: 10.0, + subImage: { x: 0, y: 0, width: 10, height: 10 }, + imageURL: CLOSE_NOTIFICATION_ICON, + color: { red: 255, green: 255, blue: 255 }, visible: true, - imageURL: imageProperties.path, alpha: backgroundAlpha }; - notify(notice, button, imageHeight, imageProperties, true); - } - return notificationText; -} - -var CLOSE_NOTIFICATION_ICON = Script.resolvePath("assets/images/close-small-light.svg"); - -// This function creates and sizes the overlays -function createNotification(text, notificationType, imageProperties) { - var count = (text.match(/\n/g) || []).length, - breakPoint = 43.0, // length when new line is added - extraLine = 0, - breaks = 0, - height = 40.0, - stack = 0, - level, - noticeProperties, - bLevel, - buttonProperties, - i; - - if (text.length >= breakPoint) { - breaks = count; - } - extraLine = breaks * 16.0; - for (i = 0; i < heights.length; i += 1) { - stack = stack + heights[i]; - } - - level = (stack + 20.0); - height = height + extraLine; - - noticeProperties = { - x: overlayLocationX, - y: level, - width: width, - height: height, - color: textColor, - backgroundColor: backColor, - alpha: backgroundAlpha, - topMargin: topMargin, - leftMargin: leftMargin, - font: {size: fontSize}, - text: text - }; - - bLevel = level + 12.0; - buttonProperties = { - x: buttonLocationX, - y: bLevel, - width: 10.0, - height: 10.0, - subImage: { x: 0, y: 0, width: 10, height: 10 }, - imageURL: CLOSE_NOTIFICATION_ICON, - color: { red: 255, green: 255, blue: 255}, - visible: true, - alpha: backgroundAlpha - }; - - if (Menu.isOptionChecked(PLAY_NOTIFICATION_SOUNDS_MENU_ITEM) && - Menu.isOptionChecked(NotificationType.getMenuString(notificationType))) { - randomSounds.playRandom(); - } - - return notify(noticeProperties, buttonProperties, height, imageProperties); -} - -function deleteNotification(index) { - var notificationTextID = notifications[index]; - if (notificationTextID == lodTextID) { - lodTextID = false; - } - Overlays.deleteOverlay(notificationTextID); - Overlays.deleteOverlay(buttons[index]); - notifications.splice(index, 1); - buttons.splice(index, 1); - times.splice(index, 1); - heights.splice(index, 1); - myAlpha.splice(index, 1); - overlay3DDetails.splice(index, 1); - arrays.splice(index, 1); -} - -// wraps whole word to newline -function stringDivider(str, slotWidth, spaceReplacer) { - var left, right; - - if (str.length > slotWidth && slotWidth > 0) { - left = str.substring(0, slotWidth); - right = str.substring(slotWidth); - return left + spaceReplacer + stringDivider(right, slotWidth, spaceReplacer); - } - return str; -} - -// formats string to add newline every 43 chars -function wordWrap(str) { - return stringDivider(str, 43.0, "\n"); -} - -function update() { - var nextOverlay, - noticeOut, - buttonOut, - arraysOut, - positions, - i, - j, - k; - - if (isOnHMD !== HMD.active) { - while (arrays.length > 0) { - deleteNotification(0); + if (Menu.isOptionChecked(PLAY_NOTIFICATION_SOUNDS_MENU_ITEM) && + Menu.isOptionChecked(NotificationType.getMenuString(notificationType))) { + randomSounds.playRandom(); } - isOnHMD = !isOnHMD; - persistTime = isOnHMD ? PERSIST_TIME_3D : PERSIST_TIME_2D; - return; + + return notify(noticeProperties, buttonProperties, height, imageProperties); } - frame += 1; - if ((frame % 60.0) === 0) { // only update once a second - locationY = 20.0; - for (i = 0; i < arrays.length; i += 1) { //repositions overlays as others fade - nextOverlay = Overlays.getOverlayAtPoint({ x: overlayLocationX, y: locationY }); - Overlays.editOverlay(notifications[i], { x: overlayLocationX, y: locationY }); - Overlays.editOverlay(buttons[i], { x: buttonLocationX, y: locationY + 12.0 }); - if (isOnHMD) { - positions = calculate3DOverlayPositions(overlay3DDetails[i].width, - overlay3DDetails[i].height, locationY); - overlay3DDetails[i].notificationOrientation = positions.notificationOrientation; - overlay3DDetails[i].notificationPosition = positions.notificationPosition; - overlay3DDetails[i].buttonPosition = positions.buttonPosition; + function deleteNotification(index) { + var notificationTextID = notifications[index]; + if (notificationTextID == lodTextID) { + lodTextID = false; + } + Overlays.deleteOverlay(notificationTextID); + Overlays.deleteOverlay(buttons[index]); + notifications.splice(index, 1); + buttons.splice(index, 1); + times.splice(index, 1); + heights.splice(index, 1); + myAlpha.splice(index, 1); + overlay3DDetails.splice(index, 1); + arrays.splice(index, 1); + } + + // wraps whole word to newline + function stringDivider(str, slotWidth, spaceReplacer) { + var left, right; + + if (str.length > slotWidth && slotWidth > 0) { + left = str.substring(0, slotWidth); + right = str.substring(slotWidth); + return left + spaceReplacer + stringDivider(right, slotWidth, spaceReplacer); + } + return str; + } + + // formats string to add newline every 43 chars + function wordWrap(str) { + return stringDivider(str, 43.0, "\n"); + } + + function update() { + var nextOverlay, + noticeOut, + buttonOut, + arraysOut, + positions, + i, + j, + k; + + if (isOnHMD !== HMD.active) { + while (arrays.length > 0) { + deleteNotification(0); } - locationY = locationY + arrays[i][3]; + isOnHMD = !isOnHMD; + persistTime = isOnHMD ? PERSIST_TIME_3D : PERSIST_TIME_2D; + return; } - } - // This checks the age of the notification and prepares to fade it after 9.0 seconds (var persistTime - 1) - for (i = 0; i < arrays.length; i += 1) { - if (ready) { - j = arrays[i][2]; - k = j + persistTime; - if (k < (new Date().getTime() / 1000)) { - ready = false; - noticeOut = arrays[i][0]; - buttonOut = arrays[i][1]; - arraysOut = i; - fadeOut(noticeOut, buttonOut, arraysOut); + frame += 1; + if ((frame % 60.0) === 0) { // only update once a second + locationY = 20.0; + for (i = 0; i < arrays.length; i += 1) { //repositions overlays as others fade + nextOverlay = Overlays.getOverlayAtPoint({ x: overlayLocationX, y: locationY }); + Overlays.editOverlay(notifications[i], { x: overlayLocationX, y: locationY }); + Overlays.editOverlay(buttons[i], { x: buttonLocationX, y: locationY + 12.0 }); + if (isOnHMD) { + positions = calculate3DOverlayPositions(overlay3DDetails[i].width, + overlay3DDetails[i].height, locationY); + overlay3DDetails[i].notificationOrientation = positions.notificationOrientation; + overlay3DDetails[i].notificationPosition = positions.notificationPosition; + overlay3DDetails[i].buttonPosition = positions.buttonPosition; + } + locationY = locationY + arrays[i][3]; + } + } + + // This checks the age of the notification and prepares to fade it after 9.0 seconds (var persistTime - 1) + for (i = 0; i < arrays.length; i += 1) { + if (ready) { + j = arrays[i][2]; + k = j + persistTime; + if (k < (new Date().getTime() / 1000)) { + ready = false; + noticeOut = arrays[i][0]; + buttonOut = arrays[i][1]; + arraysOut = i; + fadeOut(noticeOut, buttonOut, arraysOut); + } } } } -} -var STARTUP_TIMEOUT = 500, // ms - startingUp = true, - startupTimer = null; + var STARTUP_TIMEOUT = 500, // ms + startingUp = true, + startupTimer = null; -function finishStartup() { - startingUp = false; - Script.clearTimeout(startupTimer); -} + function finishStartup() { + startingUp = false; + Script.clearTimeout(startupTimer); + } -function isStartingUp() { - // Is starting up until get no checks that it is starting up for STARTUP_TIMEOUT - if (startingUp) { - if (startupTimer) { - Script.clearTimeout(startupTimer); + function isStartingUp() { + // Is starting up until get no checks that it is starting up for STARTUP_TIMEOUT + if (startingUp) { + if (startupTimer) { + Script.clearTimeout(startupTimer); + } + startupTimer = Script.setTimeout(finishStartup, STARTUP_TIMEOUT); } - startupTimer = Script.setTimeout(finishStartup, STARTUP_TIMEOUT); - } - return startingUp; -} - -function onDomainConnectionRefused(reason) { - createNotification("Connection refused: " + reason, NotificationType.CONNECTION_REFUSED); -} - -function onEditError(msg) { - createNotification(wordWrap(msg), NotificationType.EDIT_ERROR); -} - - -function onSnapshotTaken(path, notify) { - if (notify) { - var imageProperties = { - path: "file:///" + path, - aspectRatio: Window.innerWidth / Window.innerHeight - }; - createNotification(wordWrap("Snapshot saved to " + path), NotificationType.SNAPSHOT, imageProperties); - } -} - -// handles mouse clicks on buttons -function mousePressEvent(event) { - var pickRay, - clickedOverlay, - i; - - if (isOnHMD) { - pickRay = Camera.computePickRay(event.x, event.y); - clickedOverlay = Overlays.findRayIntersection(pickRay).overlayID; - } else { - clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y }); + return startingUp; } - for (i = 0; i < buttons.length; i += 1) { - if (clickedOverlay === buttons[i]) { - deleteNotification(i); + function onDomainConnectionRefused(reason) { + createNotification("Connection refused: " + reason, NotificationType.CONNECTION_REFUSED); + } + + function onEditError(msg) { + createNotification(wordWrap(msg), NotificationType.EDIT_ERROR); + } + + + function onSnapshotTaken(pathStillSnapshot, pathAnimatedSnapshot, notify) { + if (notify) { + var imageProperties = { + path: "file:///" + pathStillSnapshot, + aspectRatio: Window.innerWidth / Window.innerHeight + }; + createNotification(wordWrap("Snapshot saved to " + pathStillSnapshot), NotificationType.SNAPSHOT, imageProperties); } } -} -// Control key remains active only while key is held down -function keyReleaseEvent(key) { - if (key.key === 16777249) { - ctrlIsPressed = false; + // handles mouse clicks on buttons + function mousePressEvent(event) { + var pickRay, + clickedOverlay, + i; + + if (isOnHMD) { + pickRay = Camera.computePickRay(event.x, event.y); + clickedOverlay = Overlays.findRayIntersection(pickRay).overlayID; + } else { + clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y }); + } + + for (i = 0; i < buttons.length; i += 1) { + if (clickedOverlay === buttons[i]) { + deleteNotification(i); + } + } } -} -// Triggers notification on specific key driven events -function keyPressEvent(key) { - if (key.key === 16777249) { - ctrlIsPressed = true; + // Control key remains active only while key is held down + function keyReleaseEvent(key) { + if (key.key === 16777249) { + ctrlIsPressed = false; + } } -} -function setup() { - Menu.addMenu(MENU_NAME); - var checked = Settings.getValue(PLAY_NOTIFICATION_SOUNDS_SETTING); - checked = checked === '' ? true : checked; - Menu.addMenuItem({ - menuName: MENU_NAME, - menuItemName: PLAY_NOTIFICATION_SOUNDS_MENU_ITEM, - isCheckable: true, - isChecked: Settings.getValue(PLAY_NOTIFICATION_SOUNDS_SETTING) - }); - Menu.addSeparator(MENU_NAME, "Play sounds for:"); - for (var type in NotificationType.properties) { - checked = Settings.getValue(PLAY_NOTIFICATION_SOUNDS_TYPE_SETTING_PRE + (parseInt(type) + 1)); + // Triggers notification on specific key driven events + function keyPressEvent(key) { + if (key.key === 16777249) { + ctrlIsPressed = true; + } + } + + function setup() { + Menu.addMenu(MENU_NAME); + var checked = Settings.getValue(PLAY_NOTIFICATION_SOUNDS_SETTING); checked = checked === '' ? true : checked; Menu.addMenuItem({ menuName: MENU_NAME, - menuItemName: NotificationType.properties[type].text + NOTIFICATION_MENU_ITEM_POST, + menuItemName: PLAY_NOTIFICATION_SOUNDS_MENU_ITEM, isCheckable: true, - isChecked: checked + isChecked: Settings.getValue(PLAY_NOTIFICATION_SOUNDS_SETTING) }); + Menu.addSeparator(MENU_NAME, "Play sounds for:"); + for (var type in NotificationType.properties) { + checked = Settings.getValue(PLAY_NOTIFICATION_SOUNDS_TYPE_SETTING_PRE + (parseInt(type) + 1)); + checked = checked === '' ? true : checked; + Menu.addMenuItem({ + menuName: MENU_NAME, + menuItemName: NotificationType.properties[type].text + NOTIFICATION_MENU_ITEM_POST, + isCheckable: true, + isChecked: checked + }); + } } -} -// When our script shuts down, we should clean up all of our overlays -function scriptEnding() { - for (var i = 0; i < notifications.length; i++) { - Overlays.deleteOverlay(notifications[i]); - Overlays.deleteOverlay(buttons[i]); + // When our script shuts down, we should clean up all of our overlays + function scriptEnding() { + for (var i = 0; i < notifications.length; i++) { + Overlays.deleteOverlay(notifications[i]); + Overlays.deleteOverlay(buttons[i]); + } + Menu.removeMenu(MENU_NAME); } - Menu.removeMenu(MENU_NAME); -} -function menuItemEvent(menuItem) { - if (menuItem === PLAY_NOTIFICATION_SOUNDS_MENU_ITEM) { - Settings.setValue(PLAY_NOTIFICATION_SOUNDS_SETTING, Menu.isOptionChecked(PLAY_NOTIFICATION_SOUNDS_MENU_ITEM)); - return; + function menuItemEvent(menuItem) { + if (menuItem === PLAY_NOTIFICATION_SOUNDS_MENU_ITEM) { + Settings.setValue(PLAY_NOTIFICATION_SOUNDS_SETTING, Menu.isOptionChecked(PLAY_NOTIFICATION_SOUNDS_MENU_ITEM)); + return; + } + var notificationType = NotificationType.getTypeFromMenuItem(menuItem); + if (notificationType !== notificationType.UNKNOWN) { + Settings.setValue(PLAY_NOTIFICATION_SOUNDS_TYPE_SETTING_PRE + notificationType, Menu.isOptionChecked(menuItem)); + } } - var notificationType = NotificationType.getTypeFromMenuItem(menuItem); - if (notificationType !== notificationType.UNKNOWN) { - Settings.setValue(PLAY_NOTIFICATION_SOUNDS_TYPE_SETTING_PRE + notificationType, Menu.isOptionChecked(menuItem)); - } -} -LODManager.LODDecreased.connect(function() { - var warningText = "\n" + - "Due to the complexity of the content, the \n" + - "level of detail has been decreased. " + - "You can now see: \n" + - LODManager.getLODFeedbackText(); + LODManager.LODDecreased.connect(function () { + var warningText = "\n" + + "Due to the complexity of the content, the \n" + + "level of detail has been decreased. " + + "You can now see: \n" + + LODManager.getLODFeedbackText(); - if (lodTextID === false) { - lodTextID = createNotification(warningText, NotificationType.LOD_WARNING); - } else { - Overlays.editOverlay(lodTextID, { text: warningText }); - } -}); + if (lodTextID === false) { + lodTextID = createNotification(warningText, NotificationType.LOD_WARNING); + } else { + Overlays.editOverlay(lodTextID, { text: warningText }); + } + }); -Controller.keyPressEvent.connect(keyPressEvent); -Controller.mousePressEvent.connect(mousePressEvent); -Controller.keyReleaseEvent.connect(keyReleaseEvent); -Script.update.connect(update); -Script.scriptEnding.connect(scriptEnding); -Menu.menuItemEvent.connect(menuItemEvent); -Window.domainConnectionRefused.connect(onDomainConnectionRefused); -Window.snapshotTaken.connect(onSnapshotTaken); -Window.notifyEditError = onEditError; + Controller.keyPressEvent.connect(keyPressEvent); + Controller.mousePressEvent.connect(mousePressEvent); + Controller.keyReleaseEvent.connect(keyReleaseEvent); + Script.update.connect(update); + Script.scriptEnding.connect(scriptEnding); + Menu.menuItemEvent.connect(menuItemEvent); + Window.domainConnectionRefused.connect(onDomainConnectionRefused); + Window.snapshotTaken.connect(onSnapshotTaken); + Window.notifyEditError = onEditError; -setup(); + setup(); }()); // END LOCAL_SCOPE diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 5eebadd02f..bdb54d313f 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -120,11 +120,11 @@ function onClicked() { // take snapshot (with no notification) Script.setTimeout(function () { - Window.takeSnapshot(false, 1.91); + Window.takeSnapshot(false, true, 1.91); }, SNAPSHOT_DELAY); } -function resetButtons(path, notify) { +function resetButtons(pathStillSnapshot, pathAnimatedSnapshot, notify) { // show overlays if they were on if (resetOverlays) { Menu.setIsOptionChecked("Overlays", true); @@ -141,7 +141,8 @@ function resetButtons(path, notify) { // last element in data array tells dialog whether we can share or not confirmShare([ - { localPath: path }, + { localPath: pathAnimatedSnapshot }, + { localPath: pathStillSnapshot }, { canShare: !!location.placename, openFeedAfterShare: shouldOpenFeedAfterShare()