bring over some previous tablet-ui work

This commit is contained in:
Seth Alves 2016-12-08 16:54:39 -08:00
parent 401f49ff52
commit 369f61e172
15 changed files with 322 additions and 53 deletions

View file

@ -1018,7 +1018,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
toggleMenuUnderReticle();
}
} else if (action == controller::toInt(controller::Action::CONTEXT_MENU)) {
toggleMenuUnderReticle();
toggleTabletUI();
} else if (action == controller::toInt(controller::Action::RETICLE_X)) {
auto oldPos = getApplicationCompositor().getReticlePosition();
getApplicationCompositor().setReticlePosition({ oldPos.x + state, oldPos.y });
@ -1484,6 +1484,11 @@ void Application::toggleMenuUnderReticle() const {
offscreenUi->toggleMenu(QPoint(reticlePosition.x - X_LEFT_SHIFT, reticlePosition.y));
}
void Application::toggleTabletUI() const {
auto HMD = DependencyManager::get<HMDScriptingInterface>();
HMD->toggleShouldShowTablet();
}
void Application::checkChangeCursor() {
QMutexLocker locker(&_changeCursorLock);
if (_cursorNeedsChanging) {

View file

@ -455,6 +455,7 @@ private:
void maybeToggleMenuVisible(QMouseEvent* event) const;
void toggleMenuUnderReticle() const;
void toggleTabletUI() const;
MainWindow* _window;
QElapsedTimer& _sessionRunTimer;

View file

@ -18,6 +18,7 @@
#include <display-plugins/CompositorHelper.h>
#include <OffscreenUi.h>
#include <plugins/PluginUtils.h>
#include <QUuid>
#include "Application.h"

View file

@ -28,6 +28,8 @@ class HMDScriptingInterface : public AbstractHMDScriptingInterface, public Depen
Q_PROPERTY(glm::vec3 position READ getPosition)
Q_PROPERTY(glm::quat orientation READ getOrientation)
Q_PROPERTY(bool mounted READ isMounted)
Q_PROPERTY(bool showTablet READ getShouldShowTablet)
Q_PROPERTY(QUuid tabletID READ getCurrentTableUIID WRITE setCurrentTabletUIID)
public:
Q_INVOKABLE glm::vec3 calculateRayUICollisionPoint(const glm::vec3& position, const glm::vec3& direction) const;
@ -52,11 +54,11 @@ public:
Q_INVOKABLE void disableExtraLaser() const;
/// Suppress the activation of any on-screen keyboard so that a script operation will
/// Suppress the activation of any on-screen keyboard so that a script operation will
/// not be interrupted by a keyboard popup
/// Returns false if there is already an active keyboard displayed.
/// Clients should re-enable the keyboard when the operation is complete and ensure
/// that they balance any call to suppressKeyboard() that returns true with a corresponding
/// that they balance any call to suppressKeyboard() that returns true with a corresponding
/// call to unsuppressKeyboard() within a reasonable amount of time
Q_INVOKABLE bool suppressKeyboard();
@ -79,10 +81,19 @@ public:
bool isMounted() const;
void toggleShouldShowTablet() { _showTablet = !_showTablet; }
bool getShouldShowTablet() const { return _showTablet; }
void setCurrentTabletUIID(QUuid tabletID) { _tabletUIID = tabletID; }
QUuid getCurrentTableUIID() { return _tabletUIID; }
private:
bool _showTablet { false };
QUuid _tabletUIID; // this is the entityID of the WebEntity which is part of (a child of) the tablet-ui.
// Get the position of the HMD
glm::vec3 getPosition() const;
// Get the orientation of the HMD
glm::quat getOrientation() const;

View file

@ -18,7 +18,6 @@
#include <GeometryCache.h>
#include <PerfStat.h>
#include <gl/OffscreenQmlSurface.h>
#include <AbstractViewStateInterface.h>
#include <GLMHelpers.h>
#include <PathUtils.h>
@ -69,6 +68,8 @@ bool RenderableWebEntityItem::buildWebSurface(QSharedPointer<EntityTreeRenderer>
qWarning() << "Too many concurrent web views to create new view";
return false;
}
qDebug() << "Building web surface";
QString javaScriptToInject;
QFile webChannelFile(":qtwebchannel/qwebchannel.js");
QFile createGlobalEventBridgeFile(PathUtils::resourcesPath() + "/html/createGlobalEventBridge.js");
@ -78,13 +79,13 @@ bool RenderableWebEntityItem::buildWebSurface(QSharedPointer<EntityTreeRenderer>
QString createGlobalEventBridgeStr = QTextStream(&createGlobalEventBridgeFile).readAll();
// concatenate these js files
javaScriptToInject = webChannelStr + createGlobalEventBridgeStr;
_javaScriptToInject = webChannelStr + createGlobalEventBridgeStr;
} else {
qCWarning(entitiesrenderer) << "unable to find qwebchannel.js or createGlobalEventBridge.js";
}
// Save the original GL context, because creating a QML surface will create a new context
QOpenGLContext * currentContext = QOpenGLContext::currentContext();
QOpenGLContext* currentContext = QOpenGLContext::currentContext();
if (!currentContext) {
return false;
}
@ -117,23 +118,13 @@ bool RenderableWebEntityItem::buildWebSurface(QSharedPointer<EntityTreeRenderer>
// is no longer valid
_webSurface->create(currentContext);
#define AJT_TABLET_HACK
loadSourceURL();
#ifdef AJT_TABLET_HACK
_webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "qml/hifi/tablet/"));
_webSurface->load("Tablet.qml", [&](QQmlContext* context, QObject* obj) {
;
});
_webSurface->resume();
#else
_webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/controls/"));
_webSurface->load("WebView.qml", [&](QQmlContext* context, QObject* obj) {
context->setContextProperty("eventBridgeJavaScriptToInject", QVariant(javaScriptToInject));
});
_webSurface->resume();
// _connection = QObject::connect(_webSurface, &OffscreenQmlSurface::textureUpdated, [&](GLuint textureId) {
// _texture = textureId;
_webSurface->getRootItem()->setProperty("url", _sourceUrl);
_webSurface->getRootContext()->setContextProperty("desktop", QVariant());
#endif
// FIXME - Keyboard HMD only: Possibly add "HMDinfo" object to context for WebView.qml.
// forward web events to EntityScriptingInterface
@ -170,7 +161,9 @@ bool RenderableWebEntityItem::buildWebSurface(QSharedPointer<EntityTreeRenderer>
QTouchEvent* touchEvent = new QTouchEvent(QEvent::TouchEnd, nullptr, Qt::NoModifier, Qt::TouchPointReleased, touchPoints);
touchEvent->setWindow(_webSurface->getWindow());
touchEvent->setDevice(&_touchDevice);
touchEvent->setTarget(_webSurface->getRootItem());
if (_contentType == htmlContent) {
touchEvent->setTarget(_webSurface->getRootItem());
}
QCoreApplication::postEvent(_webSurface->getWindow(), touchEvent);
}
});
@ -256,13 +249,49 @@ void RenderableWebEntityItem::render(RenderArgs* args) {
DependencyManager::get<GeometryCache>()->renderQuad(batch, topLeft, bottomRight, texMin, texMax, glm::vec4(1.0f, 1.0f, 1.0f, fadeRatio), _geometryId);
}
void RenderableWebEntityItem::loadSourceURL() {
QUrl sourceUrl(_sourceUrl);
if (sourceUrl.scheme() == "http" || sourceUrl.scheme() == "https" ||
_sourceUrl.toLower().endsWith(".htm") || _sourceUrl.toLower().endsWith(".html")) {
qDebug() << "HERE RenderableWebEntityItem::loadSourceURL http -- " << _sourceUrl;
_contentType = htmlContent;
_webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "qml/controls/"));
QString fuh = QUrl::fromLocalFile(PathUtils::resourcesPath()).toString() + "qml/controls/" + "WebView.qml";
qDebug() << "HERE full name is " << fuh;
_webSurface->load("WebView.qml", [&](QQmlContext* context, QObject* obj) {
context->setContextProperty("eventBridgeJavaScriptToInject", QVariant(_javaScriptToInject));
});
// _webSurface->getRootItem()->setProperty("eventBridge", QVariant::fromValue(_webEntityAPIHelper));
_webSurface->getRootItem()->setProperty("url", _sourceUrl);
_webSurface->getRootContext()->setContextProperty("desktop", QVariant());
// _webSurface->getRootContext()->setContextProperty("webEntity", _webEntityAPIHelper);
} else {
qDebug() << "HERE RenderableWebEntityItem::loadSourceURL qml -- " << _sourceUrl;
_contentType = qmlContent;
_webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath()));
_webSurface->load(_sourceUrl, [&](QQmlContext* context, QObject* obj) { });
}
}
void RenderableWebEntityItem::setSourceUrl(const QString& value) {
if (_sourceUrl != value) {
qDebug() << "Setting web entity source URL to " << value;
_sourceUrl = value;
if (_webSurface) {
qDebug() << "HERE RenderableWebEntityItem::setSourceUrl has _webSurface";
AbstractViewStateInterface::instance()->postLambdaEvent([this] {
_webSurface->getRootItem()->setProperty("url", _sourceUrl);
qDebug() << "HERE RenderableWebEntityItem::setSourceUrl in lambda";
loadSourceURL();
if (_contentType == htmlContent) {
_webSurface->getRootItem()->setProperty("url", _sourceUrl);
}
});
}
}
@ -333,7 +362,9 @@ void RenderableWebEntityItem::handlePointerEvent(const PointerEvent& event) {
QTouchEvent* touchEvent = new QTouchEvent(type);
touchEvent->setWindow(_webSurface->getWindow());
touchEvent->setDevice(&_touchDevice);
touchEvent->setTarget(_webSurface->getRootItem());
if (_contentType == htmlContent) {
touchEvent->setTarget(_webSurface->getRootItem());
}
touchEvent->setTouchPoints(touchPoints);
touchEvent->setTouchPointStates(touchPointState);
@ -385,6 +416,74 @@ bool RenderableWebEntityItem::isTransparent() {
return fadeRatio < OPAQUE_ALPHA_THRESHOLD;
}
QObject* RenderableWebEntityItem::getRootItem() {
if (_webSurface) {
return dynamic_cast<QObject*>(_webSurface->getRootItem());
}
qDebug() << "HERE no _websurface";
return nullptr;
}
// UTF-8 encoded symbols
static const uint8_t UPWARDS_WHITE_ARROW_FROM_BAR[] = { 0xE2, 0x87, 0xAA, 0x00 }; // shift
static const uint8_t LEFT_ARROW[] = { 0xE2, 0x86, 0x90, 0x00 }; // backspace
static const uint8_t LEFTWARD_WHITE_ARROW[] = { 0xE2, 0x87, 0xA6, 0x00 }; // left arrow
static const uint8_t RIGHTWARD_WHITE_ARROW[] = { 0xE2, 0x87, 0xA8, 0x00 }; // right arrow
static const uint8_t ASTERISIM[] = { 0xE2, 0x81, 0x82, 0x00 }; // symbols
static const uint8_t RETURN_SYMBOL[] = { 0xE2, 0x8F, 0x8E, 0x00 }; // return
static const char PUNCTUATION_STRING[] = "&123";
static const char ALPHABET_STRING[] = "abc";
static bool equals(const QByteArray& byteArray, const uint8_t* ptr) {
int i;
for (i = 0; i < byteArray.size(); i++) {
if ((char)ptr[i] != byteArray[i]) {
return false;
}
}
return ptr[i] == 0x00;
}
void RenderableWebEntityItem::synthesizeKeyPress(QString key) {
auto utf8Key = key.toUtf8();
int scanCode = (int)utf8Key[0];
QString keyString = key;
if (equals(utf8Key, UPWARDS_WHITE_ARROW_FROM_BAR) || equals(utf8Key, ASTERISIM) ||
equals(utf8Key, (uint8_t*)PUNCTUATION_STRING) || equals(utf8Key, (uint8_t*)ALPHABET_STRING)) {
return; // ignore
} else if (equals(utf8Key, LEFT_ARROW)) {
scanCode = Qt::Key_Backspace;
keyString = "\x08";
} else if (equals(utf8Key, RETURN_SYMBOL)) {
scanCode = Qt::Key_Return;
keyString = "\x0d";
} else if (equals(utf8Key, LEFTWARD_WHITE_ARROW)) {
scanCode = Qt::Key_Left;
keyString = "";
} else if (equals(utf8Key, RIGHTWARD_WHITE_ARROW)) {
scanCode = Qt::Key_Right;
keyString = "";
}
QKeyEvent* pressEvent = new QKeyEvent(QEvent::KeyPress, scanCode, Qt::NoModifier, keyString);
QKeyEvent* releaseEvent = new QKeyEvent(QEvent::KeyRelease, scanCode, Qt::NoModifier, keyString);
QCoreApplication::postEvent(getEventHandler(), pressEvent);
QCoreApplication::postEvent(getEventHandler(), releaseEvent);
}
// void RenderableWebEntityItem::setKeyboardRaised(bool raised) {
//
// // raise the keyboard only while in HMD mode and it's being requested.
// bool value = AbstractViewStateInterface::instance()->isHMDMode() && raised;
//
// if (_contentType == htmlContent) {
// _webSurface->getRootItem()->setProperty("keyboardRaised", QVariant(value));
// }
// }
void RenderableWebEntityItem::emitScriptEvent(const QVariant& message) {
if (_webSurface) {
_webSurface->emitScriptEvent(message);

View file

@ -16,6 +16,7 @@
#include <gl/OffscreenQmlSurface.h>
#include <WebEntityItem.h>
#include <gl/OffscreenQmlSurface.h>
#include "RenderableEntityItem.h"
@ -33,6 +34,7 @@ public:
~RenderableWebEntityItem();
virtual void render(RenderArgs* args) override;
void loadSourceURL();
virtual void setSourceUrl(const QString& value) override;
virtual bool wantsHandControllerPointerEvents() const override { return true; }
@ -51,6 +53,11 @@ public:
virtual bool isTransparent() override;
public:
void synthesizeKeyPress(QString key);
QObject* getRootItem() override;
private:
bool buildWebSurface(QSharedPointer<EntityTreeRenderer> renderer);
void destroyWebSurface();
@ -69,6 +76,14 @@ private:
QMetaObject::Connection _mouseReleaseConnection;
QMetaObject::Connection _mouseMoveConnection;
QMetaObject::Connection _hoverLeaveConnection;
QString _javaScriptToInject;
enum contentType {
htmlContent,
qmlContent
};
contentType _contentType;
int _geometryId { 0 };
};

View file

@ -25,7 +25,7 @@
#include "QVariantGLM.h"
#include "SimulationOwner.h"
#include "ZoneEntityItem.h"
#include "WebEntityItem.h"
EntityScriptingInterface::EntityScriptingInterface(bool bidOnSimulationOwnership) :
_entityTree(NULL),
@ -1453,3 +1453,13 @@ void EntityScriptingInterface::setCostMultiplier(float value) {
costMultiplier = value;
}
QObject* EntityScriptingInterface::getWebViewRoot(const QUuid& entityID) {
if (auto entity = checkForTreeEntityAndTypeMatch(entityID, EntityTypes::Web)) {
auto webEntity = std::dynamic_pointer_cast<WebEntityItem>(entity);
QObject* root = webEntity->getRootItem();
qDebug() << "HERE getWebViewRoot root =" << ((void*) root);
return root;
} else {
return nullptr;
}
}

View file

@ -276,6 +276,8 @@ public slots:
Q_INVOKABLE void emitScriptEvent(const EntityItemID& entityID, const QVariant& message);
Q_INVOKABLE QObject* getWebViewRoot(const QUuid& entityID);
signals:
void collisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision);

View file

@ -59,6 +59,8 @@ public:
void setDPI(uint16_t value);
uint16_t getDPI() const;
virtual QObject* getRootItem() { return nullptr; }
protected:
QString _sourceUrl;
uint16_t _dpi;

View file

@ -34,7 +34,8 @@ var DEFAULT_SCRIPTS = [
"system/firstPersonHMD.js",
"system/snapshot.js",
"system/help.js",
"system/bubble.js"
"system/bubble.js",
"system/tablet-ui/tablet-ui.js"
];
// add a menu item for debugging

View file

@ -1267,6 +1267,9 @@ function MyController(hand) {
var grabbableProps = entityPropertiesCache.getGrabbableProps(entityID);
var grabProps = entityPropertiesCache.getGrabProps(entityID);
var props = entityPropertiesCache.getProps(entityID);
if (!props) {
return false;
}
var physical = propsArePhysical(props);
var grabbable = false;
var debug = (WANT_DEBUG_SEARCH_NAME && props.name === WANT_DEBUG_SEARCH_NAME);
@ -1546,7 +1549,7 @@ function MyController(hand) {
Entities.sendHoverOverEntity(entity, pointerEvent);
}
if (this.triggerSmoothedGrab() && !isEditing()) {
if (this.triggerSmoothedGrab() && (!isEditing() || name == "WebTablet Web")) {
this.grabbedEntity = entity;
this.setState(STATE_ENTITY_TOUCHING, "begin touching entity '" + name + "'");
return;

View file

@ -39,54 +39,72 @@ function calcSpawnInfo() {
}
// ctor
WebTablet = function (url, width, dpi, clientOnly) {
WebTablet = function (url, width, dpi, location, clientOnly) {
var ASPECT = 4.0 / 3.0;
var WIDTH = width || DEFAULT_WIDTH;
var HEIGHT = WIDTH * ASPECT;
var DEPTH = 0.025;
var DPI = dpi || DEFAULT_DPI;
var _this = this;
var spawnInfo = calcSpawnInfo();
var tabletEntityPosition = spawnInfo.position;
var tabletEntityRotation = spawnInfo.rotation;
this.tabletEntityID = Entities.addEntity({
name: "tablet",
var tabletProperties = {
name: "WebTablet Tablet",
type: "Model",
modelURL: TABLET_URL,
position: tabletEntityPosition,
rotation: tabletEntityRotation,
userData: JSON.stringify({
"grabbableKey": {"grabbable": true}
}),
dimensions: {x: WIDTH, y: HEIGHT, z: DEPTH},
parentID: MyAvatar.sessionUUID,
parentJointIndex: -2
}, clientOnly);
}
if (location) {
tabletProperties.localPosition = location.localPosition;
tabletProperties.localRotation = location.localRotation;
} else {
var spawnInfo = calcSpawnInfo();
tabletProperties.position = spawnInfo.position;
tabletProperties.rotation = spawnInfo.rotation;
}
this.tabletEntityID = Entities.addEntity(tabletProperties, clientOnly);
var WEB_ENTITY_REDUCTION_FACTOR = {x: 0.78, y: 0.85};
var WEB_ENTITY_Z_OFFSET = -0.01;
var webEntityRotation = Quat.multiply(spawnInfo.rotation, Quat.angleAxis(180, Y_AXIS));
var webEntityPosition = Vec3.sum(spawnInfo.position, Vec3.multiply(WEB_ENTITY_Z_OFFSET, Quat.getFront(webEntityRotation)));
this.createWebEntity = function(url) {
if (_this.webEntityID) {
Entities.deleteEntity(_this.webEntityID);
}
_this.webEntityID = Entities.addEntity({
name: "WebTablet Web",
type: "Web",
sourceUrl: url,
dimensions: {x: WIDTH * WEB_ENTITY_REDUCTION_FACTOR.x,
y: HEIGHT * WEB_ENTITY_REDUCTION_FACTOR.y,
z: 0.1},
localPosition: { x: 0, y: 0, z: WEB_ENTITY_Z_OFFSET },
localRotation: Quat.angleAxis(180, Y_AXIS),
shapeType: "box",
dpi: DPI,
parentID: _this.tabletEntityID,
parentJointIndex: -1
}, clientOnly);
}
this.webEntityID = Entities.addEntity({
name: "web",
type: "Web",
sourceUrl: url,
dimensions: {x: WIDTH * WEB_ENTITY_REDUCTION_FACTOR.x,
y: HEIGHT * WEB_ENTITY_REDUCTION_FACTOR.y,
z: 0.1},
position: webEntityPosition,
rotation: webEntityRotation,
shapeType: "box",
dpi: DPI,
parentID: this.tabletEntityID,
parentJointIndex: -1
}, clientOnly);
this.createWebEntity(url);
this.state = "idle";
this.getRoot = function() {
return Entities.getWebViewRoot(_this.webEntityID);
}
this.getLocation = function() {
return Entities.getEntityProperties(_this.tabletEntityID, ["localPosition", "localRotation"]);
};
};
WebTablet.prototype.destroy = function () {

View file

@ -1032,9 +1032,20 @@ SelectionDisplay = (function() {
var pickRay = controllerComputePickRay();
if (pickRay) {
var entityIntersection = Entities.findRayIntersection(pickRay, true);
var overlayIntersection = Overlays.findRayIntersection(pickRay);
if (entityIntersection.intersects &&
(!overlayIntersection.intersects || (entityIntersection.distance < overlayIntersection.distance))) {
// if (entityIntersection.properties.name == "WebTablet Web") {
// return;
// }
if (HMD.tabletID == entityIntersection.entityID) {
return;
}
selectionManager.setSelections([entityIntersection.entityID]);
}
}

View file

@ -44,7 +44,20 @@ function shouldShowWebTablet() {
function showMarketplace(marketplaceID) {
if (shouldShowWebTablet()) {
updateButtonState(true);
marketplaceWebTablet = new WebTablet("https://metaverse.highfidelity.com/marketplace", null, null, true);
print("HERE shouldShowWebTablet");
if (HMD.tabletID) {
print("HERE shouldShowWebTablet tabletID is ", HMD.tabletID);
UIWebTablet.createWebEntity("https://metaverse.highfidelity.com/marketplace");
HMD.tabletID = UIWebTablet.webEntityID;
} else {
print("HERE shouldShowWebTablet making new tablet");
marketplaceWebTablet = new WebTablet("https://metaverse.highfidelity.com/marketplace",
null, // width
null, // dpi
null, // location
true); // client-only
}
Settings.setValue(persistenceKey, marketplaceWebTablet.pickle());
} else {
var url = MARKETPLACE_URL;

View file

@ -0,0 +1,77 @@
"use strict";
//
// tablet-ui.js
//
// scripts/system/tablet-ui/
//
// Created by Seth Alves 2016-9-29
// 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
//
/* global Script, HMD, WebTablet, UIWebTablet */
(function() { // BEGIN LOCAL_SCOPE
var tabletShown = false;
var tabletLocation = null;
Script.include("../libraries/WebTablet.js");
function showTabletUI() {
tabletShown = true;
print("show tablet-ui");
// var toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system");
UIWebTablet = new WebTablet("qml/desktop/TabletUI.qml", null, null, tabletLocation);
HMD.tabletID = UIWebTablet.webEntityID;
var setUpTabletUI = function() {
var root = UIWebTablet.getRoot();
if (!root) {
print("HERE no root yet");
Script.setTimeout(setUpTabletUI, 100);
return;
}
print("HERE got root", root);
var buttons = Toolbars.getToolbarButtons("com.highfidelity.interface.toolbar.system");
print("HERE got buttons: ", buttons.length);
for (var i = 0; i < buttons.length; i++) {
print("HERE hooking up button: ", buttons[i].objectName);
Toolbars.hookUpButtonClone("com.highfidelity.interface.toolbar.system", root, buttons[i]);
}
// UserActivityLogger.openedTabletUI();
}
Script.setTimeout(setUpTabletUI, 100);
}
function hideTabletUI() {
tabletShown = false;
print("hide tablet-ui");
if (UIWebTablet) {
if (UIWebTablet.onClose) {
UIWebTablet.onClose();
}
Toolbars.destroyButtonClones("com.highfidelity.interface.toolbar.system");
tabletLocation = UIWebTablet.getLocation();
UIWebTablet.destroy();
UIWebTablet = null;
HMD.tabletID = null;
}
}
function updateShowTablet() {
if (HMD.showTablet && !tabletShown) {
showTabletUI();
} else if (!HMD.showTablet && tabletShown) {
hideTabletUI();
}
}
Script.update.connect(updateShowTablet);
// Script.setInterval(updateShowTablet, 1000);
}()); // END LOCAL_SCOPE