From 70d155048fb81763b8df70de7e71517d23b886e9 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 27 Jul 2015 14:43:32 -0700 Subject: [PATCH 01/53] Add SMI iViewHMD eye tracking as an optional library --- cmake/modules/FindiViewHMD.cmake | 27 ++++++++++++++++++++++++++ interface/CMakeLists.txt | 2 +- interface/external/iViewHMD/readme.txt | 14 +++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 cmake/modules/FindiViewHMD.cmake create mode 100644 interface/external/iViewHMD/readme.txt diff --git a/cmake/modules/FindiViewHMD.cmake b/cmake/modules/FindiViewHMD.cmake new file mode 100644 index 0000000000..eb107a2833 --- /dev/null +++ b/cmake/modules/FindiViewHMD.cmake @@ -0,0 +1,27 @@ +# +# FindiViewHMD.cmake +# +# Try to find the SMI iViewHMD eye tracker library +# +# You must provide a IVIEWHMD_ROOT_DIR which contains lib and include directories +# +# Once done this will define +# +# IVIEWHMD_FOUND - system found iViewHMD +# IVIEWHMD_INCLUDE_DIRS - the iViewHMD include directory +# IVIEWHMD_LIBRARIES - link this to use iViewHMD +# +# Created on 27 Jul 2015 by David Rowe +# Copyright 2015 High Fidelity, Inc. +# + +include("${MACRO_DIR}/HifiLibrarySearchHints.cmake") +hifi_library_search_hints("iViewHMD") + +find_path(IVIEWHMD_INCLUDE_DIRS iViewHMDAPI.h PATH_SUFFIXES include HINTS ${IVIEWHMD_SEARCH_DIRS}) +find_library(IVIEWHMD_LIBRARIES NAMES iViewHMDAPI PATH_SUFFIXES lib HINTS ${IVIEWHMD_SEARCH_DIRS}) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(iViewHMD DEFAULT_MSG IVIEWHMD_INCLUDE_DIRS IVIEWHMD_LIBRARIES) + +mark_as_advanced(IVIEWHMD_INCLUDE_DIRS IVIEWHMD_LIBRARIES IVIEWHMD_SEARCH_DIRS) diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 0c44ac801f..46a83ea707 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -2,7 +2,7 @@ set(TARGET_NAME interface) project(${TARGET_NAME}) # set a default root dir for each of our optional externals if it was not passed -set(OPTIONAL_EXTERNALS "Faceshift" "Sixense" "LeapMotion" "RtMidi" "SDL2" "RSSDK") +set(OPTIONAL_EXTERNALS "Faceshift" "Sixense" "LeapMotion" "RtMidi" "SDL2" "RSSDK" "iViewHMD") foreach(EXTERNAL ${OPTIONAL_EXTERNALS}) string(TOUPPER ${EXTERNAL} ${EXTERNAL}_UPPERCASE) if (NOT ${${EXTERNAL}_UPPERCASE}_ROOT_DIR) diff --git a/interface/external/iViewHMD/readme.txt b/interface/external/iViewHMD/readme.txt new file mode 100644 index 0000000000..7e0af7e23e --- /dev/null +++ b/interface/external/iViewHMD/readme.txt @@ -0,0 +1,14 @@ + +Instructions for adding SMI HMD Eye Tracking to Interface on Windows +David Rowe, 27 Jul 2015. + +1. Download and install the SMI HMD Eye Tracking software from http://update.smivision.com/iViewNG-HMD.exe. + +2. Copy the SDK folders (3rdParty, include, lib) from the SDK installation folder C:\Program Files (x86)\SMI\iViewNG-HMD\SDK + into the interface/externals/iViewHMD folder. This readme.txt should be there as well. + + You may optionally choose to copy the SDK folders to a location outside the repository (so you can re-use with different + checkouts and different projects). If so, set the ENV variable "HIFI_LIB_DIR" to a directory containing a subfolder + "iViewHMD" that contains the folders mentioned above. + +3. Clear your build directory, run cmake and build, and you should be all set. From e0ca6eb5edb1c8acd4a1cc1b1c77b6dce98a9934 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 27 Jul 2015 15:33:15 -0700 Subject: [PATCH 02/53] Add eye tracker class and menu item --- interface/src/Application.cpp | 22 +++++++++++++++++---- interface/src/Application.h | 2 ++ interface/src/Menu.cpp | 7 +++++++ interface/src/Menu.h | 1 + interface/src/devices/EyeTracker.cpp | 21 ++++++++++++++++++++ interface/src/devices/EyeTracker.h | 29 ++++++++++++++++++++++++++++ 6 files changed, 78 insertions(+), 4 deletions(-) create mode 100644 interface/src/devices/EyeTracker.cpp create mode 100644 interface/src/devices/EyeTracker.h diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index bb564824b0..1c7b2ff2df 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -108,12 +108,13 @@ #include "audio/AudioScope.h" #include "devices/DdeFaceTracker.h" +#include "devices/EyeTracker.h" #include "devices/Faceshift.h" #include "devices/Leapmotion.h" -#include "devices/RealSense.h" -#include "devices/SDL2Manager.h" #include "devices/MIDIManager.h" #include "devices/OculusManager.h" +#include "devices/RealSense.h" +#include "devices/SDL2Manager.h" #include "devices/TV3DManager.h" #include "scripting/AccountScriptingInterface.h" @@ -261,14 +262,14 @@ bool setupEssentials(int& argc, char** argv) { auto scriptCache = DependencyManager::set(); auto soundCache = DependencyManager::set(); auto faceshift = DependencyManager::set(); + auto ddeFaceTracker = DependencyManager::set(); + auto eyeTracker = DependencyManager::set(); auto audio = DependencyManager::set(); auto audioScope = DependencyManager::set(); auto deferredLightingEffect = DependencyManager::set(); auto textureCache = DependencyManager::set(); auto framebufferCache = DependencyManager::set(); - auto animationCache = DependencyManager::set(); - auto ddeFaceTracker = DependencyManager::set(); auto modelBlender = DependencyManager::set(); auto avatarManager = DependencyManager::set(); auto lodManager = DependencyManager::set(); @@ -635,6 +636,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : connect(ddeTracker.data(), &FaceTracker::muteToggled, this, &Application::faceTrackerMuteToggled); #endif +#ifdef HAVE_IVIEWHMD + auto eyeTracker = DependencyManager::get(); + eyeTracker->init(); +#endif + auto applicationUpdater = DependencyManager::get(); connect(applicationUpdater.data(), &AutoUpdater::newVersionIsAvailable, dialogsManager.data(), &DialogsManager::showUpdateDialog); applicationUpdater->checkForUpdate(); @@ -2007,6 +2013,13 @@ void Application::setActiveFaceTracker() { #endif } +void Application::setActiveEyeTracker() { +#ifdef HAVE_IVIEWHMD + auto eyeTracker = DependencyManager::get(); + eyeTracker->setEnabled(Menu::getInstance()->isOptionChecked(MenuOption::SMIEyeTracking)); +#endif +} + bool Application::exportEntities(const QString& filename, const QVector& entityIDs) { QVector entities; @@ -3408,6 +3421,7 @@ void Application::renderRearViewMirror(RenderArgs* renderArgs, const QRect& regi void Application::resetSensors() { DependencyManager::get()->reset(); DependencyManager::get()->reset(); + DependencyManager::get()->reset(); OculusManager::reset(); diff --git a/interface/src/Application.h b/interface/src/Application.h index d1886862d2..881d93cab2 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -406,6 +406,8 @@ public slots: void resetSensors(); void setActiveFaceTracker(); + void setActiveEyeTracker(); + void aboutApp(); void showEditEntitiesHelp(); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 91ae6a4d02..c5a92747e9 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -437,6 +437,13 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::AutoMuteAudio, 0, false); #endif +#ifdef HAVE_IVIEWHMD + MenuWrapper* eyeTrackingMenu = avatarDebugMenu->addMenu("Eye Tracking"); + QAction* smiEyeTracking = addCheckableActionToQMenuAndActionHash(eyeTrackingMenu, MenuOption::SMIEyeTracking, 0, false, + qApp, SLOT(setActiveEyeTracker())); + smiEyeTracking->setVisible(true); +#endif + auto avatarManager = DependencyManager::get(); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AvatarReceiveStats, 0, false, avatarManager.data(), SLOT(setShouldShowReceiveStats(bool))); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index bf0f89abb5..b8ac2ef189 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -273,6 +273,7 @@ namespace MenuOption { const QString SixenseEnabled = "Enable Hydra Support"; const QString SixenseMouseInput = "Enable Sixense Mouse Input"; const QString ShiftHipsForIdleAnimations = "Shift hips for idle animations"; + const QString SMIEyeTracking = "SMI Eye Tracking"; const QString Stars = "Stars"; const QString Stats = "Stats"; const QString StopAllScripts = "Stop All Scripts"; diff --git a/interface/src/devices/EyeTracker.cpp b/interface/src/devices/EyeTracker.cpp new file mode 100644 index 0000000000..3e0d9b78dd --- /dev/null +++ b/interface/src/devices/EyeTracker.cpp @@ -0,0 +1,21 @@ +// +// EyeTracker.cpp +// interface/src/devices +// +// Created by David Rowe on 27 Jul 2015. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "EyeTracker.h" + +void EyeTracker::init() { +} + +void EyeTracker::setEnabled(bool enabled) { +} + +void EyeTracker::reset() { +} diff --git a/interface/src/devices/EyeTracker.h b/interface/src/devices/EyeTracker.h new file mode 100644 index 0000000000..a5529770a9 --- /dev/null +++ b/interface/src/devices/EyeTracker.h @@ -0,0 +1,29 @@ +// +// EyeTracker.h +// interface/src/devices +// +// Created by David Rowe on 27 Jul 2015. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_EyeTracker_h +#define hifi_EyeTracker_h + +#include + +#include + +class EyeTracker : public QObject, public Dependency { + Q_OBJECT + SINGLETON_DEPENDENCY + +public slots: + void init(); + void setEnabled(bool enabled); + void reset(); +}; + +#endif // hifi_EyeTracker_h From 72a590e34cce4116618840c33499fe3d7f2f57fc Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 28 Jul 2015 11:02:56 -0700 Subject: [PATCH 03/53] Initialize, start, and stop eye tracking with simulated data --- interface/src/Application.cpp | 14 +++--- interface/src/devices/EyeTracker.cpp | 65 ++++++++++++++++++++++++++++ interface/src/devices/EyeTracker.h | 12 +++++ 3 files changed, 86 insertions(+), 5 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 1c7b2ff2df..217b8f334b 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -595,6 +595,12 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : loadScripts(); } +#ifdef HAVE_IVIEWHMD + // Do this before loading settings + auto eyeTracker = DependencyManager::get(); + eyeTracker->init(); +#endif + loadSettings(); int SAVE_SETTINGS_INTERVAL = 10 * MSECS_PER_SECOND; // Let's save every seconds for now connect(&_settingsTimer, &QTimer::timeout, this, &Application::saveSettings); @@ -636,11 +642,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : connect(ddeTracker.data(), &FaceTracker::muteToggled, this, &Application::faceTrackerMuteToggled); #endif -#ifdef HAVE_IVIEWHMD - auto eyeTracker = DependencyManager::get(); - eyeTracker->init(); -#endif - auto applicationUpdater = DependencyManager::get(); connect(applicationUpdater.data(), &AutoUpdater::newVersionIsAvailable, dialogsManager.data(), &DialogsManager::showUpdateDialog); applicationUpdater->checkForUpdate(); @@ -703,6 +704,9 @@ void Application::cleanupBeforeQuit() { #ifdef HAVE_DDE DependencyManager::destroy(); #endif +#ifdef HAVE_IVIEWHMD + DependencyManager::destroy(); +#endif } void Application::emptyLocalCache() { diff --git a/interface/src/devices/EyeTracker.cpp b/interface/src/devices/EyeTracker.cpp index 3e0d9b78dd..ce6afc1d97 100644 --- a/interface/src/devices/EyeTracker.cpp +++ b/interface/src/devices/EyeTracker.cpp @@ -11,10 +11,75 @@ #include "EyeTracker.h" +#include "InterfaceLogging.h" + +#ifdef HAVE_IVIEWHMD +static void CALLBACK eyeTrackerCallback(smi_CallbackDataStruct* data) { + auto eyeTracker = DependencyManager::get(); + if (eyeTracker) { // Guard against a few callbacks that continue to be received after smi_quit(). + eyeTracker->processData(data); + } +} +#endif + +EyeTracker::~EyeTracker() { +#ifdef HAVE_IVIEWHMD + int result = smi_quit(); + if (result != SMI_RET_SUCCESS) { + qCWarning(interfaceapp) << "Eye Tracker: Error terminating tracking:" << result; + } +#endif +} + +void EyeTracker::processData(smi_CallbackDataStruct* data) { + if (!_isEnabled) { + return; + } + +#ifdef HAVE_IVIEWHMD + if (data->type == SMI_SIMPLE_GAZE_SAMPLE) { + smi_SampleHMDStruct* sample = (smi_SampleHMDStruct*)data->result; + } +#endif +} + void EyeTracker::init() { + if (_isInitialized) { + qCWarning(interfaceapp) << "Eye Tracker: Already initialized"; + return; + } + +#ifdef HAVE_IVIEWHMD + int result = smi_setCallback(eyeTrackerCallback); + if (result != SMI_RET_SUCCESS) { + qCWarning(interfaceapp) << "Eye Tracker: Error setting callback:" << result; + } else { + _isInitialized = true; + } +#endif } void EyeTracker::setEnabled(bool enabled) { + if (!_isInitialized) { + qCWarning(interfaceapp) << "Eye Tracker: Not initialized before setting enabled"; + return; + } + +#ifdef HAVE_IVIEWHMD + qCDebug(interfaceapp) << "Eye Tracker: Set enabled =" << enabled; + + if (enabled && !_isStreaming) { + // There is no smi_stopStreaming() method so start streaming a maximum of once per program run. + int result = smi_startStreaming(true); + if (result != SMI_RET_SUCCESS) { + qCWarning(interfaceapp) << "Eye Tracker: Error starting streaming:" << result; + } else { + _isStreaming = true; + } + } + + _isEnabled = enabled && _isStreaming; +#endif } void EyeTracker::reset() { diff --git a/interface/src/devices/EyeTracker.h b/interface/src/devices/EyeTracker.h index a5529770a9..1410da3669 100644 --- a/interface/src/devices/EyeTracker.h +++ b/interface/src/devices/EyeTracker.h @@ -15,15 +15,27 @@ #include #include +#include + class EyeTracker : public QObject, public Dependency { Q_OBJECT SINGLETON_DEPENDENCY +public: + ~EyeTracker(); + + void processData(smi_CallbackDataStruct* data); + public slots: void init(); void setEnabled(bool enabled); void reset(); + +private: + bool _isInitialized = false; + bool _isStreaming = false; + bool _isEnabled = false; }; #endif // hifi_EyeTracker_h From f0ed8d8cefdf4cdf327e497e2d9628b1b1a9bee9 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 28 Jul 2015 14:07:25 -0700 Subject: [PATCH 04/53] Add menu item that enables / disables simulated eye tracking --- interface/src/Application.cpp | 15 ++++++++------- interface/src/Menu.cpp | 5 +++-- interface/src/Menu.h | 1 + interface/src/devices/EyeTracker.cpp | 21 ++++++++++----------- interface/src/devices/EyeTracker.h | 2 +- 5 files changed, 23 insertions(+), 21 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 217b8f334b..fcbe64cb06 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -595,12 +595,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : loadScripts(); } -#ifdef HAVE_IVIEWHMD - // Do this before loading settings - auto eyeTracker = DependencyManager::get(); - eyeTracker->init(); -#endif - loadSettings(); int SAVE_SETTINGS_INTERVAL = 10 * MSECS_PER_SECOND; // Let's save every seconds for now connect(&_settingsTimer, &QTimer::timeout, this, &Application::saveSettings); @@ -642,6 +636,12 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : connect(ddeTracker.data(), &FaceTracker::muteToggled, this, &Application::faceTrackerMuteToggled); #endif +#ifdef HAVE_IVIEWHMD + auto eyeTracker = DependencyManager::get(); + eyeTracker->init(); + setActiveEyeTracker(); +#endif + auto applicationUpdater = DependencyManager::get(); connect(applicationUpdater.data(), &AutoUpdater::newVersionIsAvailable, dialogsManager.data(), &DialogsManager::showUpdateDialog); applicationUpdater->checkForUpdate(); @@ -2020,7 +2020,8 @@ void Application::setActiveFaceTracker() { void Application::setActiveEyeTracker() { #ifdef HAVE_IVIEWHMD auto eyeTracker = DependencyManager::get(); - eyeTracker->setEnabled(Menu::getInstance()->isOptionChecked(MenuOption::SMIEyeTracking)); + eyeTracker->setEnabled(Menu::getInstance()->isOptionChecked(MenuOption::SMIEyeTracking), + Menu::getInstance()->isOptionChecked(MenuOption::SimulateEyeTracking)); #endif } diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index c5a92747e9..fed7b84791 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -439,9 +439,10 @@ Menu::Menu() { #ifdef HAVE_IVIEWHMD MenuWrapper* eyeTrackingMenu = avatarDebugMenu->addMenu("Eye Tracking"); - QAction* smiEyeTracking = addCheckableActionToQMenuAndActionHash(eyeTrackingMenu, MenuOption::SMIEyeTracking, 0, false, + addCheckableActionToQMenuAndActionHash(eyeTrackingMenu, MenuOption::SMIEyeTracking, 0, false, + qApp, SLOT(setActiveEyeTracker())); + addCheckableActionToQMenuAndActionHash(eyeTrackingMenu, MenuOption::SimulateEyeTracking, 0, false, qApp, SLOT(setActiveEyeTracker())); - smiEyeTracking->setVisible(true); #endif auto avatarManager = DependencyManager::get(); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index b8ac2ef189..c1f7bcf751 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -273,6 +273,7 @@ namespace MenuOption { const QString SixenseEnabled = "Enable Hydra Support"; const QString SixenseMouseInput = "Enable Sixense Mouse Input"; const QString ShiftHipsForIdleAnimations = "Shift hips for idle animations"; + const QString SimulateEyeTracking = "Simulate"; const QString SMIEyeTracking = "SMI Eye Tracking"; const QString Stars = "Stars"; const QString Stats = "Stats"; diff --git a/interface/src/devices/EyeTracker.cpp b/interface/src/devices/EyeTracker.cpp index ce6afc1d97..7f0dcb4510 100644 --- a/interface/src/devices/EyeTracker.cpp +++ b/interface/src/devices/EyeTracker.cpp @@ -59,26 +59,25 @@ void EyeTracker::init() { #endif } -void EyeTracker::setEnabled(bool enabled) { +void EyeTracker::setEnabled(bool enabled, bool simulate) { if (!_isInitialized) { - qCWarning(interfaceapp) << "Eye Tracker: Not initialized before setting enabled"; return; } #ifdef HAVE_IVIEWHMD - qCDebug(interfaceapp) << "Eye Tracker: Set enabled =" << enabled; - - if (enabled && !_isStreaming) { - // There is no smi_stopStreaming() method so start streaming a maximum of once per program run. - int result = smi_startStreaming(true); + qCDebug(interfaceapp) << "Eye Tracker: Set enabled =" << enabled << ", simulate =" << simulate; + bool success = true; + int result = 0; + if (enabled) { + // There is no smi_stopStreaming() method so keep streaming once started in case tracking is re-enabled after stopping. + result = smi_startStreaming(simulate); if (result != SMI_RET_SUCCESS) { - qCWarning(interfaceapp) << "Eye Tracker: Error starting streaming:" << result; - } else { - _isStreaming = true; + qCWarning(interfaceapp) << "Eye Tracker: Error starting streaming:" << smiReturnValueToString(result); + success = false; } } - _isEnabled = enabled && _isStreaming; + _isEnabled = enabled && success; #endif } diff --git a/interface/src/devices/EyeTracker.h b/interface/src/devices/EyeTracker.h index 1410da3669..ab8a071dd3 100644 --- a/interface/src/devices/EyeTracker.h +++ b/interface/src/devices/EyeTracker.h @@ -29,7 +29,7 @@ public: public slots: void init(); - void setEnabled(bool enabled); + void setEnabled(bool enabled, bool simulate); void reset(); private: From 8f0663841c9be4a4d0cd54220ef2bf97ba4ccb03 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 28 Jul 2015 14:09:19 -0700 Subject: [PATCH 05/53] Provide more user-friendly eye tracking error messages --- interface/src/devices/EyeTracker.cpp | 38 ++++++++++++++++++++++++++-- interface/src/devices/EyeTracker.h | 2 ++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/interface/src/devices/EyeTracker.cpp b/interface/src/devices/EyeTracker.cpp index 7f0dcb4510..04e6364e33 100644 --- a/interface/src/devices/EyeTracker.cpp +++ b/interface/src/devices/EyeTracker.cpp @@ -11,6 +11,8 @@ #include "EyeTracker.h" +#include + #include "InterfaceLogging.h" #ifdef HAVE_IVIEWHMD @@ -26,7 +28,7 @@ EyeTracker::~EyeTracker() { #ifdef HAVE_IVIEWHMD int result = smi_quit(); if (result != SMI_RET_SUCCESS) { - qCWarning(interfaceapp) << "Eye Tracker: Error terminating tracking:" << result; + qCWarning(interfaceapp) << "Eye Tracker: Error terminating tracking:" << smiReturnValueToString(result); } #endif } @@ -52,7 +54,8 @@ void EyeTracker::init() { #ifdef HAVE_IVIEWHMD int result = smi_setCallback(eyeTrackerCallback); if (result != SMI_RET_SUCCESS) { - qCWarning(interfaceapp) << "Eye Tracker: Error setting callback:" << result; + qCWarning(interfaceapp) << "Eye Tracker: Error setting callback:" << smiReturnValueToString(result); + QMessageBox::warning(nullptr, "Eye Tracker Error", smiReturnValueToString(result)); } else { _isInitialized = true; } @@ -78,8 +81,39 @@ void EyeTracker::setEnabled(bool enabled, bool simulate) { } _isEnabled = enabled && success; + + if (!success) { + // Display error dialog after updating _isEnabled. + QMessageBox::warning(nullptr, "Eye Tracker Error", smiReturnValueToString(result)); + } #endif } void EyeTracker::reset() { } + +QString EyeTracker::smiReturnValueToString(int value) { + switch (value) + { + case smi_ErrorReturnValue::SMI_ERROR_NO_CALLBACK_SET: + return "No callback set"; + case smi_ErrorReturnValue::SMI_ERROR_CONNECTING_TO_HMD: + return "Error connecting to HMD"; + case smi_ErrorReturnValue::SMI_ERROR_HMD_NOT_SUPPORTED: + return "HMD not supported"; + case smi_ErrorReturnValue::SMI_ERROR_NOT_IMPLEMENTED: + return "Not implmented"; + case smi_ErrorReturnValue::SMI_ERROR_INVALID_PARAMETER: + return "Invalid parameter"; + case smi_ErrorReturnValue::SMI_ERROR_EYECAMERAS_NOT_AVAILABLE: + return "Eye cameras not available"; + case smi_ErrorReturnValue::SMI_ERROR_OCULUS_RUNTIME_NOT_SUPPORTED: + return "Oculus runtime not supported"; + case smi_ErrorReturnValue::SMI_ERROR_UNKNOWN: + return "Unknown error"; + default: + QString number; + number.setNum(value); + return number; + } +} diff --git a/interface/src/devices/EyeTracker.h b/interface/src/devices/EyeTracker.h index ab8a071dd3..31c6648712 100644 --- a/interface/src/devices/EyeTracker.h +++ b/interface/src/devices/EyeTracker.h @@ -33,6 +33,8 @@ public slots: void reset(); private: + QString smiReturnValueToString(int value); + bool _isInitialized = false; bool _isStreaming = false; bool _isEnabled = false; From 1ae920368ddcd5e717b6f1f5472d6bebd7c08993 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 29 Jul 2015 08:38:20 -0700 Subject: [PATCH 06/53] Calculate and look at the point the eye tracker user is looking at --- interface/src/Application.cpp | 15 ++++++--- interface/src/devices/EyeTracker.cpp | 49 ++++++++++++++++++++++++++++ interface/src/devices/EyeTracker.h | 9 ++++- 3 files changed, 67 insertions(+), 6 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index fcbe64cb06..5861adc704 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2282,7 +2282,8 @@ void Application::updateMyAvatarLookAtPosition() { PerformanceWarning warn(showWarnings, "Application::updateMyAvatarLookAtPosition()"); _myAvatar->updateLookAtTargetAvatar(); - FaceTracker* tracker = getActiveFaceTracker(); + FaceTracker* faceTracker = getActiveFaceTracker(); + auto eyeTracker = DependencyManager::get(); bool isLookingAtSomeone = false; glm::vec3 lookAtSpot; @@ -2294,6 +2295,10 @@ void Application::updateMyAvatarLookAtPosition() { } else { lookAtSpot = _myCamera.getPosition() + OculusManager::getMidEyePosition(); } + } else if (eyeTracker->isTracking()) { + // Look at the point that the user is looking at. + lookAtSpot = _myAvatar->getHead()->getEyePosition() + + (_myAvatar->getHead()->getFinalOrientationInWorldFrame() * eyeTracker->getLookAtPosition()); } else { AvatarSharedPointer lookingAt = _myAvatar->getLookAtTargetAvatar().lock(); if (lookingAt && _myAvatar != lookingAt.get()) { @@ -2331,12 +2336,12 @@ void Application::updateMyAvatarLookAtPosition() { } // Deflect the eyes a bit to match the detected gaze from the face tracker if active. - if (tracker && !tracker->isMuted()) { - float eyePitch = tracker->getEstimatedEyePitch(); - float eyeYaw = tracker->getEstimatedEyeYaw(); + if (faceTracker && !faceTracker->isMuted()) { + float eyePitch = faceTracker->getEstimatedEyePitch(); + float eyeYaw = faceTracker->getEstimatedEyeYaw(); const float GAZE_DEFLECTION_REDUCTION_DURING_EYE_CONTACT = 0.1f; glm::vec3 origin = _myAvatar->getHead()->getEyePosition(); - float deflection = tracker->getEyeDeflection(); + float deflection = faceTracker->getEyeDeflection(); if (isLookingAtSomeone) { deflection *= GAZE_DEFLECTION_REDUCTION_DURING_EYE_CONTACT; } diff --git a/interface/src/devices/EyeTracker.cpp b/interface/src/devices/EyeTracker.cpp index 04e6364e33..f13dd0f61a 100644 --- a/interface/src/devices/EyeTracker.cpp +++ b/interface/src/devices/EyeTracker.cpp @@ -14,6 +14,7 @@ #include #include "InterfaceLogging.h" +#include "OctreeConstants.h" #ifdef HAVE_IVIEWHMD static void CALLBACK eyeTrackerCallback(smi_CallbackDataStruct* data) { @@ -40,7 +41,55 @@ void EyeTracker::processData(smi_CallbackDataStruct* data) { #ifdef HAVE_IVIEWHMD if (data->type == SMI_SIMPLE_GAZE_SAMPLE) { + // Calculate the intersections of the left and right eye look-at vectors with a vertical plane along the monocular + // gaze direction. Average these positions to give the look-at point. + // If the eyes are parallel or diverged, gaze at a distant look-at point calculated the same as for non eye tracking. + // Line-plane intersection: https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection + smi_SampleHMDStruct* sample = (smi_SampleHMDStruct*)data->result; + // The iViewHMD coordinate system has x and z axes reversed compared to Interface, i.e., wearing the HMD: + // - x is left + // - y is up + // - z is forwards + + // Plane + smi_Vec3d point = sample->gazeBasePoint; // mm + smi_Vec3d direction = sample->gazeDirection; + glm::vec3 planePoint = glm::vec3(-point.x, point.y, -point.z) / 1000.0f; + glm::vec3 planeNormal = glm::vec3(-direction.z, 0.0f, direction.x); + glm::vec3 monocularDirection = glm::vec3(-direction.x, direction.y, -direction.z); + + // Left eye + point = sample->left.gazeBasePoint; // mm + direction = sample->left.gazeDirection; + glm::vec3 leftLinePoint = glm::vec3(-point.x, point.y, -point.z) / 1000.0f; + glm::vec3 leftLineDirection = glm::vec3(-direction.x, direction.y, -direction.z); + + // Right eye + point = sample->right.gazeBasePoint; // mm + direction = sample->right.gazeDirection; + glm::vec3 rightLinePoint = glm::vec3(-point.x, point.y, -point.z) / 1000.0f; + glm::vec3 rightLineDirection = glm::vec3(-direction.x, direction.y, -direction.z); + + // Plane - line dot products + float leftLinePlaneDotProduct = glm::dot(leftLineDirection, planeNormal); + float rightLinePlaneDotProduct = glm::dot(rightLineDirection, planeNormal); + + // Gaze into distance if eyes are parallel or diverged; otherwise the look-at is the average of look-at points + if (abs(leftLinePlaneDotProduct) <= FLT_EPSILON || abs(rightLinePlaneDotProduct) <= FLT_EPSILON) { + _lookAtPosition = monocularDirection * (float)TREE_SCALE; + } else { + float leftDistance = glm::dot(planePoint - leftLinePoint, planeNormal) / leftLinePlaneDotProduct; + float rightDistance = glm::dot(planePoint - rightLinePoint, planeNormal) / rightLinePlaneDotProduct; + if (leftDistance <= 0.0f || rightDistance <= 0.0f + || leftDistance > (float)TREE_SCALE || rightDistance > (float)TREE_SCALE) { + _lookAtPosition = monocularDirection * (float)TREE_SCALE; + } else { + glm::vec3 leftIntersectionPoint = leftLinePoint + leftDistance * leftLineDirection; + glm::vec3 rightIntersectionPoint = rightLinePoint + rightDistance * rightLineDirection; + _lookAtPosition = (leftIntersectionPoint + rightIntersectionPoint) / 2.0f; + } + } } #endif } diff --git a/interface/src/devices/EyeTracker.h b/interface/src/devices/EyeTracker.h index 31c6648712..cb970d5be5 100644 --- a/interface/src/devices/EyeTracker.h +++ b/interface/src/devices/EyeTracker.h @@ -14,6 +14,8 @@ #include +#include + #include #include @@ -24,6 +26,10 @@ class EyeTracker : public QObject, public Dependency { public: ~EyeTracker(); + + bool isTracking() { return _isEnabled; } + + glm::vec3 getLookAtPosition() { return _lookAtPosition; } // From mid eye point in head frame. void processData(smi_CallbackDataStruct* data); @@ -36,8 +42,9 @@ private: QString smiReturnValueToString(int value); bool _isInitialized = false; - bool _isStreaming = false; bool _isEnabled = false; + + glm::vec3 _lookAtPosition; }; #endif // hifi_EyeTracker_h From 76fe0c876421d6c0256fe602e67f88ab3c63d8f3 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 29 Jul 2015 11:14:00 -0700 Subject: [PATCH 07/53] Control eyes only if in HMD view or simulating --- interface/src/Application.cpp | 2 +- interface/src/devices/EyeTracker.cpp | 1 + interface/src/devices/EyeTracker.h | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 5861adc704..02056a94dc 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2295,7 +2295,7 @@ void Application::updateMyAvatarLookAtPosition() { } else { lookAtSpot = _myCamera.getPosition() + OculusManager::getMidEyePosition(); } - } else if (eyeTracker->isTracking()) { + } else if (eyeTracker->isTracking() && (isHMDMode() || eyeTracker->isSimulating())) { // Look at the point that the user is looking at. lookAtSpot = _myAvatar->getHead()->getEyePosition() + (_myAvatar->getHead()->getFinalOrientationInWorldFrame() * eyeTracker->getLookAtPosition()); diff --git a/interface/src/devices/EyeTracker.cpp b/interface/src/devices/EyeTracker.cpp index f13dd0f61a..9e2737d156 100644 --- a/interface/src/devices/EyeTracker.cpp +++ b/interface/src/devices/EyeTracker.cpp @@ -130,6 +130,7 @@ void EyeTracker::setEnabled(bool enabled, bool simulate) { } _isEnabled = enabled && success; + _isSimulating = _isEnabled && simulate; if (!success) { // Display error dialog after updating _isEnabled. diff --git a/interface/src/devices/EyeTracker.h b/interface/src/devices/EyeTracker.h index cb970d5be5..c41d529e91 100644 --- a/interface/src/devices/EyeTracker.h +++ b/interface/src/devices/EyeTracker.h @@ -28,6 +28,7 @@ public: ~EyeTracker(); bool isTracking() { return _isEnabled; } + bool isSimulating() { return _isSimulating; } glm::vec3 getLookAtPosition() { return _lookAtPosition; } // From mid eye point in head frame. @@ -43,6 +44,7 @@ private: bool _isInitialized = false; bool _isEnabled = false; + bool _isSimulating = false; glm::vec3 _lookAtPosition; }; From e5d2a7898001088ed06eae2034fb432965b1cb61 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 29 Jul 2015 11:33:21 -0700 Subject: [PATCH 08/53] Don't check SMI Eye Tracker menu item if tracking doesn't start --- interface/src/Application.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 02056a94dc..90edcc0b54 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2019,9 +2019,12 @@ void Application::setActiveFaceTracker() { void Application::setActiveEyeTracker() { #ifdef HAVE_IVIEWHMD + bool isEyeTrackingOptionChecked = Menu::getInstance()->isOptionChecked(MenuOption::SMIEyeTracking); auto eyeTracker = DependencyManager::get(); - eyeTracker->setEnabled(Menu::getInstance()->isOptionChecked(MenuOption::SMIEyeTracking), - Menu::getInstance()->isOptionChecked(MenuOption::SimulateEyeTracking)); + eyeTracker->setEnabled(isEyeTrackingOptionChecked, Menu::getInstance()->isOptionChecked(MenuOption::SimulateEyeTracking)); + if (isEyeTrackingOptionChecked && !eyeTracker->isTracking()) { + Menu::getInstance()->setIsOptionChecked(MenuOption::SMIEyeTracking, false); + } #endif } From f2ae7bfac0ad9461d67a74627afe6e26b6edafe2 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 29 Jul 2015 15:14:43 -0700 Subject: [PATCH 09/53] Don't apply saccades to tracked eyes --- interface/src/avatar/Head.cpp | 7 ++++++- interface/src/devices/EyeTracker.cpp | 1 + libraries/avatars/src/AvatarData.cpp | 6 ++++++ libraries/avatars/src/AvatarData.h | 8 ++++---- libraries/avatars/src/HeadData.cpp | 1 + libraries/avatars/src/HeadData.h | 1 + 6 files changed, 19 insertions(+), 5 deletions(-) diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp index 55f33f57a4..5232fe93c4 100644 --- a/interface/src/avatar/Head.cpp +++ b/interface/src/avatar/Head.cpp @@ -17,11 +17,13 @@ #include "Application.h" #include "Avatar.h" +#include "DependencyManager.h" #include "GeometryUtil.h" #include "Head.h" #include "Menu.h" #include "Util.h" #include "devices/DdeFaceTracker.h" +#include "devices/EyeTracker.h" #include "devices/Faceshift.h" using namespace std; @@ -116,6 +118,9 @@ void Head::simulate(float deltaTime, bool isMine, bool billboard) { applyEyelidOffset(getFinalOrientationInWorldFrame()); } } + + auto eyeTracker = DependencyManager::get(); + _isEyeTrackerConnected = eyeTracker->isTracking(); } // Twist the upper body to follow the rotation of the head, but only do this with my avatar, // since everyone else will see the full joint rotations for other people. @@ -125,7 +130,7 @@ void Head::simulate(float deltaTime, bool isMine, bool billboard) { setTorsoTwist(currentTwist + (getFinalYaw() * BODY_FOLLOW_HEAD_FACTOR - currentTwist) * BODY_FOLLOW_HEAD_YAW_RATE); } - if (!(_isFaceTrackerConnected || billboard)) { + if (!(_isFaceTrackerConnected || _isEyeTrackerConnected || billboard)) { // Update eye saccades const float AVERAGE_MICROSACCADE_INTERVAL = 1.0f; const float AVERAGE_SACCADE_INTERVAL = 6.0f; diff --git a/interface/src/devices/EyeTracker.cpp b/interface/src/devices/EyeTracker.cpp index 9e2737d156..a384202255 100644 --- a/interface/src/devices/EyeTracker.cpp +++ b/interface/src/devices/EyeTracker.cpp @@ -140,6 +140,7 @@ void EyeTracker::setEnabled(bool enabled, bool simulate) { } void EyeTracker::reset() { + // Nothing to do. } QString EyeTracker::smiReturnValueToString(int value) { diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 5b970a95a3..295b8ba8d7 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -194,6 +194,11 @@ QByteArray AvatarData::toByteArray() { if (_headData->_isFaceTrackerConnected) { setAtBit(bitItems, IS_FACESHIFT_CONNECTED); } + // eye tracker state + if (_headData->_isEyeTrackerConnected) { + setAtBit(bitItems, IS_EYE_TRACKER_CONNECTED); + } + // referential state if (_referential != NULL && _referential->isValid()) { setAtBit(bitItems, HAS_REFERENTIAL); } @@ -431,6 +436,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { + (oneAtBit(bitItems, HAND_STATE_FINGER_POINTING_BIT) ? IS_FINGER_POINTING_FLAG : 0); _headData->_isFaceTrackerConnected = oneAtBit(bitItems, IS_FACESHIFT_CONNECTED); + _headData->_isEyeTrackerConnected = oneAtBit(bitItems, IS_EYE_TRACKER_CONNECTED); bool hasReferential = oneAtBit(bitItems, HAS_REFERENTIAL); // Referential diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index a020be0f7a..7d60555abc 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -77,21 +77,21 @@ const quint32 AVATAR_MOTION_SCRIPTABLE_BITS = const qint64 AVATAR_SILENCE_THRESHOLD_USECS = 5 * USECS_PER_SECOND; -// Bitset of state flags - we store the key state, hand state, faceshift, chat circling, and existance of +// Bitset of state flags - we store the key state, hand state, Faceshift, eye tracking, and existence of // referential data in this bit set. The hand state is an octal, but is split into two sections to maintain // backward compatibility. The bits are ordered as such (0-7 left to right). // +-----+-----+-+-+-+--+ -// |K0,K1|H0,H1|F|C|R|H2| +// |K0,K1|H0,H1|F|E|R|H2| // +-----+-----+-+-+-+--+ // Key state - K0,K1 is found in the 1st and 2nd bits // Hand state - H0,H1,H2 is found in the 3rd, 4th, and 8th bits // Faceshift - F is found in the 5th bit -// Chat Circling - C is found in the 6th bit +// Eye tracker - E is found in the 6th bit // Referential Data - R is found in the 7th bit const int KEY_STATE_START_BIT = 0; // 1st and 2nd bits const int HAND_STATE_START_BIT = 2; // 3rd and 4th bits const int IS_FACESHIFT_CONNECTED = 4; // 5th bit -const int UNUSED_AVATAR_STATE_BIT_5 = 5; // 6th bit (was CHAT_CIRCLING) +const int IS_EYE_TRACKER_CONNECTED = 5; // 6th bit (was CHAT_CIRCLING) const int HAS_REFERENTIAL = 6; // 7th bit const int HAND_STATE_FINGER_POINTING_BIT = 7; // 8th bit diff --git a/libraries/avatars/src/HeadData.cpp b/libraries/avatars/src/HeadData.cpp index 7789385547..e853a3c57e 100644 --- a/libraries/avatars/src/HeadData.cpp +++ b/libraries/avatars/src/HeadData.cpp @@ -32,6 +32,7 @@ HeadData::HeadData(AvatarData* owningAvatar) : _lookAtPosition(0.0f, 0.0f, 0.0f), _audioLoudness(0.0f), _isFaceTrackerConnected(false), + _isEyeTrackerConnected(false), _leftEyeBlink(0.0f), _rightEyeBlink(0.0f), _averageLoudness(0.0f), diff --git a/libraries/avatars/src/HeadData.h b/libraries/avatars/src/HeadData.h index b180541914..3e790ff573 100644 --- a/libraries/avatars/src/HeadData.h +++ b/libraries/avatars/src/HeadData.h @@ -93,6 +93,7 @@ protected: glm::vec3 _lookAtPosition; float _audioLoudness; bool _isFaceTrackerConnected; + bool _isEyeTrackerConnected; float _leftEyeBlink; float _rightEyeBlink; float _averageLoudness; From 212327dd461096aba09dfed5ba23c83411f17ce7 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 29 Jul 2015 16:20:45 -0700 Subject: [PATCH 10/53] Fix eye tracker conditional compile --- interface/src/devices/EyeTracker.cpp | 6 ++++-- interface/src/devices/EyeTracker.h | 4 ++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/interface/src/devices/EyeTracker.cpp b/interface/src/devices/EyeTracker.cpp index a384202255..4cb205d158 100644 --- a/interface/src/devices/EyeTracker.cpp +++ b/interface/src/devices/EyeTracker.cpp @@ -34,12 +34,12 @@ EyeTracker::~EyeTracker() { #endif } +#ifdef HAVE_IVIEWHMD void EyeTracker::processData(smi_CallbackDataStruct* data) { if (!_isEnabled) { return; } -#ifdef HAVE_IVIEWHMD if (data->type == SMI_SIMPLE_GAZE_SAMPLE) { // Calculate the intersections of the left and right eye look-at vectors with a vertical plane along the monocular // gaze direction. Average these positions to give the look-at point. @@ -91,8 +91,8 @@ void EyeTracker::processData(smi_CallbackDataStruct* data) { } } } -#endif } +#endif void EyeTracker::init() { if (_isInitialized) { @@ -143,6 +143,7 @@ void EyeTracker::reset() { // Nothing to do. } +#ifdef HAVE_IVIEWHMD QString EyeTracker::smiReturnValueToString(int value) { switch (value) { @@ -168,3 +169,4 @@ QString EyeTracker::smiReturnValueToString(int value) { return number; } } +#endif diff --git a/interface/src/devices/EyeTracker.h b/interface/src/devices/EyeTracker.h index c41d529e91..b0b848a6de 100644 --- a/interface/src/devices/EyeTracker.h +++ b/interface/src/devices/EyeTracker.h @@ -17,7 +17,9 @@ #include #include +#ifdef HAVE_IVIEWHMD #include +#endif class EyeTracker : public QObject, public Dependency { @@ -32,7 +34,9 @@ public: glm::vec3 getLookAtPosition() { return _lookAtPosition; } // From mid eye point in head frame. +#ifdef HAVE_IVIEWHMD void processData(smi_CallbackDataStruct* data); +#endif public slots: void init(); From e300eb27c02cbe043fab597199068bbaedab20bb Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 3 Aug 2015 09:55:14 -0700 Subject: [PATCH 11/53] Fix libs directory path --- cmake/modules/FindiViewHMD.cmake | 4 ++-- interface/external/iViewHMD/readme.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmake/modules/FindiViewHMD.cmake b/cmake/modules/FindiViewHMD.cmake index eb107a2833..c06846b218 100644 --- a/cmake/modules/FindiViewHMD.cmake +++ b/cmake/modules/FindiViewHMD.cmake @@ -3,7 +3,7 @@ # # Try to find the SMI iViewHMD eye tracker library # -# You must provide a IVIEWHMD_ROOT_DIR which contains lib and include directories +# You must provide a IVIEWHMD_ROOT_DIR which contains 3rdParty, include, and libs directories # # Once done this will define # @@ -19,7 +19,7 @@ include("${MACRO_DIR}/HifiLibrarySearchHints.cmake") hifi_library_search_hints("iViewHMD") find_path(IVIEWHMD_INCLUDE_DIRS iViewHMDAPI.h PATH_SUFFIXES include HINTS ${IVIEWHMD_SEARCH_DIRS}) -find_library(IVIEWHMD_LIBRARIES NAMES iViewHMDAPI PATH_SUFFIXES lib HINTS ${IVIEWHMD_SEARCH_DIRS}) +find_library(IVIEWHMD_LIBRARIES NAMES iViewHMDAPI PATH_SUFFIXES libs HINTS ${IVIEWHMD_SEARCH_DIRS}) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(iViewHMD DEFAULT_MSG IVIEWHMD_INCLUDE_DIRS IVIEWHMD_LIBRARIES) diff --git a/interface/external/iViewHMD/readme.txt b/interface/external/iViewHMD/readme.txt index 7e0af7e23e..4b3d59349b 100644 --- a/interface/external/iViewHMD/readme.txt +++ b/interface/external/iViewHMD/readme.txt @@ -4,7 +4,7 @@ David Rowe, 27 Jul 2015. 1. Download and install the SMI HMD Eye Tracking software from http://update.smivision.com/iViewNG-HMD.exe. -2. Copy the SDK folders (3rdParty, include, lib) from the SDK installation folder C:\Program Files (x86)\SMI\iViewNG-HMD\SDK +2. Copy the SDK folders (3rdParty, include, libs) from the SDK installation folder C:\Program Files (x86)\SMI\iViewNG-HMD\SDK into the interface/externals/iViewHMD folder. This readme.txt should be there as well. You may optionally choose to copy the SDK folders to a location outside the repository (so you can re-use with different From 86ad490bdef2897f3038becaa1a2b5435c87b7a1 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 3 Aug 2015 20:52:11 -0700 Subject: [PATCH 12/53] Fix to build on build PC without iViewHMD software installed --- cmake/modules/FindiViewHMD.cmake | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/cmake/modules/FindiViewHMD.cmake b/cmake/modules/FindiViewHMD.cmake index c06846b218..f4b7f832d0 100644 --- a/cmake/modules/FindiViewHMD.cmake +++ b/cmake/modules/FindiViewHMD.cmake @@ -15,13 +15,29 @@ # Copyright 2015 High Fidelity, Inc. # -include("${MACRO_DIR}/HifiLibrarySearchHints.cmake") -hifi_library_search_hints("iViewHMD") +if (WIN32) -find_path(IVIEWHMD_INCLUDE_DIRS iViewHMDAPI.h PATH_SUFFIXES include HINTS ${IVIEWHMD_SEARCH_DIRS}) -find_library(IVIEWHMD_LIBRARIES NAMES iViewHMDAPI PATH_SUFFIXES libs HINTS ${IVIEWHMD_SEARCH_DIRS}) + include("${MACRO_DIR}/HifiLibrarySearchHints.cmake") + hifi_library_search_hints("iViewHMD") -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(iViewHMD DEFAULT_MSG IVIEWHMD_INCLUDE_DIRS IVIEWHMD_LIBRARIES) + find_path(IVIEWHMD_INCLUDE_DIRS iViewHMDAPI.h PATH_SUFFIXES include HINTS ${IVIEWHMD_SEARCH_DIRS}) -mark_as_advanced(IVIEWHMD_INCLUDE_DIRS IVIEWHMD_LIBRARIES IVIEWHMD_SEARCH_DIRS) + find_library(IVIEWHMD_LIBRARIES NAMES iViewHMDAPI PATH_SUFFIXES libs HINTS ${IVIEWHMD_SEARCH_DIRS}) + find_path(IVIEWHMD_DLL_PATH iViewHMDAPI.dll PATH_SUFFIXES libs HINTS ${IVIEWHMD_SEARCH_DIRS}) + + find_path(LIBIVIEWNG_LIBCORE_DLL_PATH libiViewNG-LibCore.dll PATH_SUFFIXES 3rdParty HINTS ${IVIEWHMD_SEARCH_DIRS}) + find_path(OPENCV_HIGHGUI220_DLL_PATH opencv_highgui220.dll PATH_SUFFIXES 3rdParty HINTS ${IVIEWHMD_SEARCH_DIRS}) + find_path(OPENCV_CORE200_DLL_PATH opencv_core220.dll PATH_SUFFIXES 3rdParty HINTS ${IVIEWHMD_SEARCH_DIRS}) + find_path(OPENCV_IMGPROC220_DLL_PATH opencv_imgproc220.dll PATH_SUFFIXES 3rdParty HINTS ${IVIEWHMD_SEARCH_DIRS}) + + list(APPEND IVIEWHMD_REQUIREMENTS IVIEWHMD_INCLUDE_DIRS IVIEWHMD_LIBRARIES IVIEWHMD_DLL_PATH) + list(APPEND IVIEWHMD_REQUIREMENTS LIBIVIEWNG_LIBCORE_DLL_PATH OPENCV_HIGHGUI220_DLL_PATH OPENCV_CORE200_DLL_PATH OPENCV_IMGPROC220_DLL_PATH) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(IVIEWHMD DEFAULT_MSG ${IVIEWHMD_REQUIREMENTS}) + + add_paths_to_fixup_libs(${IVIEWHMD_DLL_PATH} ${LIBIVIEWNG_LIBCORE_DLL_PATH} ${OPENCV_HIGHGUI220_DLL_PATH} ${OPENCV_CORE200_DLL_PATH} ${OPENCV_IMGPROC220_DLL_PATH}) + + mark_as_advanced(IVIEWHMD_INCLUDE_DIRS IVIEWHMD_LIBRARIES IVIEWHMD_SEARCH_DIRS) + +endif () From 12010737093608f178d931c3388241941ed8958e Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 4 Aug 2015 13:15:26 -0700 Subject: [PATCH 13/53] Add DLLs needed for Interface to run without iViewHMD installed --- cmake/modules/FindiViewHMD.cmake | 45 ++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/cmake/modules/FindiViewHMD.cmake b/cmake/modules/FindiViewHMD.cmake index f4b7f832d0..f7b13f8124 100644 --- a/cmake/modules/FindiViewHMD.cmake +++ b/cmake/modules/FindiViewHMD.cmake @@ -21,23 +21,46 @@ if (WIN32) hifi_library_search_hints("iViewHMD") find_path(IVIEWHMD_INCLUDE_DIRS iViewHMDAPI.h PATH_SUFFIXES include HINTS ${IVIEWHMD_SEARCH_DIRS}) - find_library(IVIEWHMD_LIBRARIES NAMES iViewHMDAPI PATH_SUFFIXES libs HINTS ${IVIEWHMD_SEARCH_DIRS}) - find_path(IVIEWHMD_DLL_PATH iViewHMDAPI.dll PATH_SUFFIXES libs HINTS ${IVIEWHMD_SEARCH_DIRS}) + find_path(IVIEWHMD_API_DLL_PATH iViewHMDAPI.dll PATH_SUFFIXES libs HINTS ${IVIEWHMD_SEARCH_DIRS}) + list(APPEND IVIEWHMD_REQUIREMENTS IVIEWHMD_INCLUDE_DIRS IVIEWHMD_LIBRARIES IVIEWHMD_API_DLL_PATH) - find_path(LIBIVIEWNG_LIBCORE_DLL_PATH libiViewNG-LibCore.dll PATH_SUFFIXES 3rdParty HINTS ${IVIEWHMD_SEARCH_DIRS}) - find_path(OPENCV_HIGHGUI220_DLL_PATH opencv_highgui220.dll PATH_SUFFIXES 3rdParty HINTS ${IVIEWHMD_SEARCH_DIRS}) - find_path(OPENCV_CORE200_DLL_PATH opencv_core220.dll PATH_SUFFIXES 3rdParty HINTS ${IVIEWHMD_SEARCH_DIRS}) - find_path(OPENCV_IMGPROC220_DLL_PATH opencv_imgproc220.dll PATH_SUFFIXES 3rdParty HINTS ${IVIEWHMD_SEARCH_DIRS}) - - list(APPEND IVIEWHMD_REQUIREMENTS IVIEWHMD_INCLUDE_DIRS IVIEWHMD_LIBRARIES IVIEWHMD_DLL_PATH) - list(APPEND IVIEWHMD_REQUIREMENTS LIBIVIEWNG_LIBCORE_DLL_PATH OPENCV_HIGHGUI220_DLL_PATH OPENCV_CORE200_DLL_PATH OPENCV_IMGPROC220_DLL_PATH) + set(IVIEWHMD_DLLS + avcodec-53.dll + avformat-53.dll + avutil-51.dll + libboost_filesystem-mgw45-mt-1_49.dll + libboost_system-mgw45-mt-1_49.dll + libboost_thread-mgw45-mt-1_49.dll + libgcc_s_dw2-1.dll + libiViewNG-LibCore.dll + libopencv_calib3d244.dll + libopencv_core244.dll + libopencv_features2d244.dll + libopencv_flann244.dll + libopencv_highgui244.dll + libopencv_imgproc244.dll + libopencv_legacy244.dll + libopencv_ml244.dll + libopencv_video244.dll + libstdc++-6.dll + opencv_core220.dll + opencv_highgui220.dll + opencv_imgproc220.dll + swscale-2.dll + ) + + foreach(IVIEWHMD_DLL ${IVIEWHMD_DLLS}) + find_path(IVIEWHMD_DLL_PATH ${IVIEWHMD_DLL} PATH_SUFFIXES 3rdParty HINTS ${IVIEWHMD_SEARCH_DIRS}) + list(APPEND IVIEWHMD_REQUIREMENTS IVIEWHMD_DLL_PATH) + list(APPEND IVIEWHMD_DLL_PATHS ${IVIEWHMD_DLL_PATH}) + endforeach() include(FindPackageHandleStandardArgs) find_package_handle_standard_args(IVIEWHMD DEFAULT_MSG ${IVIEWHMD_REQUIREMENTS}) - add_paths_to_fixup_libs(${IVIEWHMD_DLL_PATH} ${LIBIVIEWNG_LIBCORE_DLL_PATH} ${OPENCV_HIGHGUI220_DLL_PATH} ${OPENCV_CORE200_DLL_PATH} ${OPENCV_IMGPROC220_DLL_PATH}) + add_paths_to_fixup_libs(${IVIEWHMD_API_DLL_PATH} ${IVIEWHMD_DLL_PATHS}) mark_as_advanced(IVIEWHMD_INCLUDE_DIRS IVIEWHMD_LIBRARIES IVIEWHMD_SEARCH_DIRS) -endif () +endif() From fefddb631fb50cfe7fbd6a2a2a1af93974f0912b Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 4 Aug 2015 14:46:03 -0700 Subject: [PATCH 14/53] Add 1 and 3 point eye tracker calibration --- interface/src/Application.cpp | 24 +++++++++++++++++--- interface/src/Application.h | 2 ++ interface/src/Menu.cpp | 4 ++++ interface/src/Menu.h | 2 ++ interface/src/devices/EyeTracker.cpp | 33 ++++++++++++++++++++++++++++ interface/src/devices/EyeTracker.h | 2 ++ 6 files changed, 64 insertions(+), 3 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index f7381396a0..a0429090ba 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2034,12 +2034,30 @@ void Application::setActiveFaceTracker() { void Application::setActiveEyeTracker() { #ifdef HAVE_IVIEWHMD - bool isEyeTrackingOptionChecked = Menu::getInstance()->isOptionChecked(MenuOption::SMIEyeTracking); + bool isEyeTracking = Menu::getInstance()->isOptionChecked(MenuOption::SMIEyeTracking); + bool isSimulating = Menu::getInstance()->isOptionChecked(MenuOption::SimulateEyeTracking); auto eyeTracker = DependencyManager::get(); - eyeTracker->setEnabled(isEyeTrackingOptionChecked, Menu::getInstance()->isOptionChecked(MenuOption::SimulateEyeTracking)); - if (isEyeTrackingOptionChecked && !eyeTracker->isTracking()) { + eyeTracker->setEnabled(isEyeTracking, isSimulating); + if (isEyeTracking && !eyeTracker->isTracking()) { Menu::getInstance()->setIsOptionChecked(MenuOption::SMIEyeTracking, false); + isEyeTracking = false; } + Menu::getInstance()->getActionForOption(MenuOption::Calibrate1Point)->setEnabled(isEyeTracking && !isSimulating); + Menu::getInstance()->getActionForOption(MenuOption::Calibrate3Points)->setEnabled(isEyeTracking && !isSimulating); +#endif +} + +void Application::calibrateEyeTracker1Point() { +#ifdef HAVE_IVIEWHMD + auto eyeTracker = DependencyManager::get(); + eyeTracker->calibrate(1); +#endif +} + +void Application::calibrateEyeTracker3Points() { +#ifdef HAVE_IVIEWHMD + auto eyeTracker = DependencyManager::get(); + eyeTracker->calibrate(3); #endif } diff --git a/interface/src/Application.h b/interface/src/Application.h index 877e4c4a0d..d8ebc65db7 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -409,6 +409,8 @@ public slots: void setActiveFaceTracker(); void setActiveEyeTracker(); + void calibrateEyeTracker1Point(); + void calibrateEyeTracker3Points(); void aboutApp(); void showEditEntitiesHelp(); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 51d07d538d..34c4c9aada 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -440,6 +440,10 @@ Menu::Menu() { MenuWrapper* eyeTrackingMenu = avatarDebugMenu->addMenu("Eye Tracking"); addCheckableActionToQMenuAndActionHash(eyeTrackingMenu, MenuOption::SMIEyeTracking, 0, false, qApp, SLOT(setActiveEyeTracker())); + addActionToQMenuAndActionHash(eyeTrackingMenu, MenuOption::Calibrate1Point, 0, + qApp, SLOT(calibrateEyeTracker1Point())); + addActionToQMenuAndActionHash(eyeTrackingMenu, MenuOption::Calibrate3Points, 0, + qApp, SLOT(calibrateEyeTracker3Points())); addCheckableActionToQMenuAndActionHash(eyeTrackingMenu, MenuOption::SimulateEyeTracking, 0, false, qApp, SLOT(setActiveEyeTracker())); #endif diff --git a/interface/src/Menu.h b/interface/src/Menu.h index b3531e4140..33944674c0 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -156,6 +156,8 @@ namespace MenuOption { const QString Bookmarks = "Bookmarks"; const QString CascadedShadows = "Cascaded"; const QString CachesSize = "RAM Caches Size"; + const QString Calibrate1Point = "Calibrate - 1 Point..."; + const QString Calibrate3Points = "Calibrate - 3 Points..."; const QString CalibrateCamera = "Calibrate Camera"; const QString CenterPlayerInView = "Center Player In View"; const QString Chat = "Chat..."; diff --git a/interface/src/devices/EyeTracker.cpp b/interface/src/devices/EyeTracker.cpp index 4cb205d158..39308aedfc 100644 --- a/interface/src/devices/EyeTracker.cpp +++ b/interface/src/devices/EyeTracker.cpp @@ -143,6 +143,39 @@ void EyeTracker::reset() { // Nothing to do. } +#ifdef HAVE_IVIEWHMD +void EyeTracker::calibrate(int points) { + smi_CalibrationHMDStruct* calibrationHMDStruct; + smi_createCalibrationHMDStruct(&calibrationHMDStruct); + + smi_CalibrationTypeEnum calibrationType; + switch (points) { + case 1: + calibrationType = SMI_ONE_POINT_CALIBRATION; + qCDebug(interfaceapp) << "Eye Tracker: One point calibration"; + break; + case 3: + calibrationType = SMI_THREE_POINT_CALIBRATION; + qCDebug(interfaceapp) << "Eye Tracker: Three point calibration"; + break; + default: + qCWarning(interfaceapp) << "Eye Tracker: Invalid calibration specified"; + return; + } + + calibrationHMDStruct->type = calibrationType; + calibrationHMDStruct->backgroundColor->blue = 0.5; + calibrationHMDStruct->backgroundColor->green = 0.5; + calibrationHMDStruct->backgroundColor->red = 0.5; + calibrationHMDStruct->foregroundColor->blue = 1.0; + calibrationHMDStruct->foregroundColor->green = 1.0; + calibrationHMDStruct->foregroundColor->red = 1.0; + + smi_setupCalibration(calibrationHMDStruct); + smi_calibrate(); +} +#endif + #ifdef HAVE_IVIEWHMD QString EyeTracker::smiReturnValueToString(int value) { switch (value) diff --git a/interface/src/devices/EyeTracker.h b/interface/src/devices/EyeTracker.h index b0b848a6de..57950a7062 100644 --- a/interface/src/devices/EyeTracker.h +++ b/interface/src/devices/EyeTracker.h @@ -36,6 +36,8 @@ public: #ifdef HAVE_IVIEWHMD void processData(smi_CallbackDataStruct* data); + + void calibrate(int points); #endif public slots: From b5fbfc645f0e5f549c0fe762f3c43232bc40317c Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 4 Aug 2015 15:57:19 -0700 Subject: [PATCH 15/53] Tidy eye tracker interface --- interface/src/devices/EyeTracker.h | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/interface/src/devices/EyeTracker.h b/interface/src/devices/EyeTracker.h index 57950a7062..3e602bb12d 100644 --- a/interface/src/devices/EyeTracker.h +++ b/interface/src/devices/EyeTracker.h @@ -29,10 +29,14 @@ class EyeTracker : public QObject, public Dependency { public: ~EyeTracker(); - bool isTracking() { return _isEnabled; } - bool isSimulating() { return _isSimulating; } + void init(); + void setEnabled(bool enabled, bool simulate); + void reset(); - glm::vec3 getLookAtPosition() { return _lookAtPosition; } // From mid eye point in head frame. + bool isTracking() const { return _isEnabled; } + bool isSimulating() const { return _isSimulating; } + + glm::vec3 getLookAtPosition() const { return _lookAtPosition; } // From mid eye point in head frame. #ifdef HAVE_IVIEWHMD void processData(smi_CallbackDataStruct* data); @@ -40,11 +44,6 @@ public: void calibrate(int points); #endif -public slots: - void init(); - void setEnabled(bool enabled, bool simulate); - void reset(); - private: QString smiReturnValueToString(int value); From 11ec5f5ddf44f1aed9ee362c68b99391c041c5f1 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 4 Aug 2015 16:46:48 -0700 Subject: [PATCH 16/53] Don't display duplicate error message box --- interface/src/devices/EyeTracker.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/devices/EyeTracker.cpp b/interface/src/devices/EyeTracker.cpp index 39308aedfc..561f8a5a13 100644 --- a/interface/src/devices/EyeTracker.cpp +++ b/interface/src/devices/EyeTracker.cpp @@ -132,8 +132,8 @@ void EyeTracker::setEnabled(bool enabled, bool simulate) { _isEnabled = enabled && success; _isSimulating = _isEnabled && simulate; - if (!success) { - // Display error dialog after updating _isEnabled. + if (!success && result != SMI_ERROR_HMD_NOT_SUPPORTED) { + // Display error dialog after updating _isEnabled. Except if SMI SDK has already displayed an error message. QMessageBox::warning(nullptr, "Eye Tracker Error", smiReturnValueToString(result)); } #endif From 72c4ab33a1a2c9587e7d8ba6b11a7f51197b851a Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 4 Aug 2015 16:54:21 -0700 Subject: [PATCH 17/53] Handle eye calibration errors --- interface/src/devices/EyeTracker.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/interface/src/devices/EyeTracker.cpp b/interface/src/devices/EyeTracker.cpp index 561f8a5a13..0e24dde8ee 100644 --- a/interface/src/devices/EyeTracker.cpp +++ b/interface/src/devices/EyeTracker.cpp @@ -171,8 +171,20 @@ void EyeTracker::calibrate(int points) { calibrationHMDStruct->foregroundColor->green = 1.0; calibrationHMDStruct->foregroundColor->red = 1.0; - smi_setupCalibration(calibrationHMDStruct); - smi_calibrate(); + int result = smi_setupCalibration(calibrationHMDStruct); + if (result != SMI_RET_SUCCESS) { + qCWarning(interfaceapp) << "Eye Tracker: Error setting up calibration:" << smiReturnValueToString(result); + return; + } else { + result = smi_calibrate(); + if (result != SMI_RET_SUCCESS) { + qCWarning(interfaceapp) << "Eye Tracker: Error performing calibration:" << smiReturnValueToString(result); + } + } + + if (result != SMI_RET_SUCCESS) { + QMessageBox::warning(nullptr, "Eye Tracker Error", "Calibration error: " + smiReturnValueToString(result)); + } } #endif From c77157457818db1463e6bbe824e6635885dd3c75 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 4 Aug 2015 18:02:34 -0700 Subject: [PATCH 18/53] Add 5 point eye tracker calibration option --- interface/src/Application.cpp | 12 ++++++++++-- interface/src/Application.h | 1 + interface/src/Menu.cpp | 13 +++++++++---- interface/src/Menu.h | 5 +++-- 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index a0429090ba..bfc23334b7 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2042,8 +2042,9 @@ void Application::setActiveEyeTracker() { Menu::getInstance()->setIsOptionChecked(MenuOption::SMIEyeTracking, false); isEyeTracking = false; } - Menu::getInstance()->getActionForOption(MenuOption::Calibrate1Point)->setEnabled(isEyeTracking && !isSimulating); - Menu::getInstance()->getActionForOption(MenuOption::Calibrate3Points)->setEnabled(isEyeTracking && !isSimulating); + Menu::getInstance()->getActionForOption(MenuOption::OnePointCalibration)->setEnabled(isEyeTracking && !isSimulating); + Menu::getInstance()->getActionForOption(MenuOption::ThreePointCalibration)->setEnabled(isEyeTracking && !isSimulating); + Menu::getInstance()->getActionForOption(MenuOption::FivePointCalibration)->setEnabled(isEyeTracking && !isSimulating); #endif } @@ -2061,6 +2062,13 @@ void Application::calibrateEyeTracker3Points() { #endif } +void Application::calibrateEyeTracker5Points() { +#ifdef HAVE_IVIEWHMD + auto eyeTracker = DependencyManager::get(); + eyeTracker->calibrate(5); +#endif +} + bool Application::exportEntities(const QString& filename, const QVector& entityIDs) { QVector entities; diff --git a/interface/src/Application.h b/interface/src/Application.h index d8ebc65db7..aaedc63592 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -411,6 +411,7 @@ public slots: void setActiveEyeTracker(); void calibrateEyeTracker1Point(); void calibrateEyeTracker3Points(); + void calibrateEyeTracker5Points(); void aboutApp(); void showEditEntitiesHelp(); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 34c4c9aada..4a77a342a2 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -440,10 +440,15 @@ Menu::Menu() { MenuWrapper* eyeTrackingMenu = avatarDebugMenu->addMenu("Eye Tracking"); addCheckableActionToQMenuAndActionHash(eyeTrackingMenu, MenuOption::SMIEyeTracking, 0, false, qApp, SLOT(setActiveEyeTracker())); - addActionToQMenuAndActionHash(eyeTrackingMenu, MenuOption::Calibrate1Point, 0, - qApp, SLOT(calibrateEyeTracker1Point())); - addActionToQMenuAndActionHash(eyeTrackingMenu, MenuOption::Calibrate3Points, 0, - qApp, SLOT(calibrateEyeTracker3Points())); + { + MenuWrapper* calibrateEyeTrackingMenu = eyeTrackingMenu->addMenu("Calibrate"); + addActionToQMenuAndActionHash(calibrateEyeTrackingMenu, MenuOption::OnePointCalibration, 0, + qApp, SLOT(calibrateEyeTracker1Point())); + addActionToQMenuAndActionHash(calibrateEyeTrackingMenu, MenuOption::ThreePointCalibration, 0, + qApp, SLOT(calibrateEyeTracker3Point())); + addActionToQMenuAndActionHash(calibrateEyeTrackingMenu, MenuOption::FivePointCalibration, 0, + qApp, SLOT(calibrateEyeTracker5Point())); + } addCheckableActionToQMenuAndActionHash(eyeTrackingMenu, MenuOption::SimulateEyeTracking, 0, false, qApp, SLOT(setActiveEyeTracker())); #endif diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 33944674c0..70186f3307 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -156,8 +156,6 @@ namespace MenuOption { const QString Bookmarks = "Bookmarks"; const QString CascadedShadows = "Cascaded"; const QString CachesSize = "RAM Caches Size"; - const QString Calibrate1Point = "Calibrate - 1 Point..."; - const QString Calibrate3Points = "Calibrate - 3 Points..."; const QString CalibrateCamera = "Calibrate Camera"; const QString CenterPlayerInView = "Center Player In View"; const QString Chat = "Chat..."; @@ -197,6 +195,7 @@ namespace MenuOption { const QString Faceshift = "Faceshift"; const QString FilterSixense = "Smooth Sixense Movement"; const QString FirstPerson = "First Person"; + const QString FivePointCalibration = "5 Point Calibration"; const QString Forward = "Forward"; const QString FrameTimer = "Show Timer"; const QString Fullscreen = "Fullscreen"; @@ -221,6 +220,7 @@ namespace MenuOption { const QString NamesAboveHeads = "Names Above Heads"; const QString NoFaceTracking = "None"; const QString OctreeStats = "Entity Statistics"; + const QString OnePointCalibration = "1 Point Calibration"; const QString OnlyDisplayTopTen = "Only Display Top Ten"; const QString PackageModel = "Package Model..."; const QString Pair = "Pair"; @@ -283,6 +283,7 @@ namespace MenuOption { const QString SuppressShortTimings = "Suppress Timings Less than 10ms"; const QString TestPing = "Test Ping"; const QString ThirdPerson = "Third Person"; + const QString ThreePointCalibration = "3 Point Calibration"; const QString ThrottleFPSIfNotFocus = "Throttle FPS If Not Focus"; const QString ToolWindow = "Tool Window"; const QString TransmitterDrive = "Transmitter Drive"; From fae26d901cb3d30eba78277e13641aa9d885f53d Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 4 Aug 2015 18:34:56 -0700 Subject: [PATCH 19/53] Add menu option to render look-at targets --- interface/src/Menu.cpp | 1 + interface/src/Menu.h | 1 + interface/src/avatar/Avatar.cpp | 2 ++ interface/src/avatar/Avatar.h | 1 + interface/src/avatar/Head.cpp | 17 +++++++++++++++++ interface/src/avatar/Head.h | 3 +++ tests/ui/src/main.cpp | 1 + 7 files changed, 26 insertions(+) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 4a77a342a2..c124f2caec 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -461,6 +461,7 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderHeadCollisionShapes); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderBoundingCollisionShapes); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderLookAtVectors, 0, false); + addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderLookAtTargets, 0, false); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderFocusIndicator, 0, false); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ShowWhosLookingAtMe, 0, false); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 70186f3307..ce5388449f 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -234,6 +234,7 @@ namespace MenuOption { const QString RenderBoundingCollisionShapes = "Show Bounding Collision Shapes"; const QString RenderFocusIndicator = "Show Eye Focus"; const QString RenderHeadCollisionShapes = "Show Head Collision Shapes"; + const QString RenderLookAtTargets = "Show Look-at Targets"; const QString RenderLookAtVectors = "Show Look-at Vectors"; const QString RenderSkeletonCollisionShapes = "Show Skeleton Collision Shapes"; const QString RenderTargetFramerate = "Framerate"; diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index e29e5e4408..abb671f207 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -69,6 +69,8 @@ namespace render { auto avatarPtr = static_pointer_cast(avatar); bool renderLookAtVectors = Menu::getInstance()->isOptionChecked(MenuOption::RenderLookAtVectors); avatarPtr->setDisplayingLookatVectors(renderLookAtVectors); + bool renderLookAtTarget = Menu::getInstance()->isOptionChecked(MenuOption::RenderLookAtTargets); + avatarPtr->setDisplayingLookatTarget(renderLookAtTarget); if (avatarPtr->isInitialized() && args) { avatarPtr->render(args, Application::getInstance()->getCamera()->getPosition()); diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index 6035c44389..07a5ea2b89 100644 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -91,6 +91,7 @@ public: //setters void setDisplayingLookatVectors(bool displayingLookatVectors) { getHead()->setRenderLookatVectors(displayingLookatVectors); } + void setDisplayingLookatTarget(bool displayingLookatTarget) { getHead()->setRenderLookatTarget(displayingLookatTarget); } void setIsLookAtTarget(const bool isLookAtTarget) { _isLookAtTarget = isLookAtTarget; } bool getIsLookAtTarget() const { return _isLookAtTarget; } //getters diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp index 481a8ca9e8..aa7084b8e0 100644 --- a/interface/src/avatar/Head.cpp +++ b/interface/src/avatar/Head.cpp @@ -46,6 +46,7 @@ Head::Head(Avatar* owningAvatar) : _mouth3(0.0f), _mouth4(0.0f), _renderLookatVectors(false), + _renderLookatTarget(false), _saccade(0.0f, 0.0f, 0.0f), _saccadeTarget(0.0f, 0.0f, 0.0f), _leftEyeBlinkVelocity(0.0f), @@ -305,6 +306,9 @@ void Head::render(RenderArgs* renderArgs, float alpha, ViewFrustum* renderFrustu if (_renderLookatVectors) { renderLookatVectors(renderArgs, _leftEyePosition, _rightEyePosition, getCorrectedLookAtPosition()); } + if (_renderLookatTarget) { + renderLookatTarget(renderArgs, getCorrectedLookAtPosition()); + } } void Head::setScale (float scale) { @@ -414,4 +418,17 @@ void Head::renderLookatVectors(RenderArgs* renderArgs, glm::vec3 leftEyePosition geometryCache->renderLine(batch, rightEyePosition, lookatPosition, startColor, endColor, _rightEyeLookAtID); } +void Head::renderLookatTarget(RenderArgs* renderArgs, glm::vec3 lookatPosition) { + auto& batch = *renderArgs->_batch; + auto transform = Transform{}; + transform.setTranslation(lookatPosition); + batch.setModelTransform(transform); + auto deferredLighting = DependencyManager::get(); + deferredLighting->bindSimpleProgram(batch); + + auto geometryCache = DependencyManager::get(); + const float LOOK_AT_TARGET_RADIUS = 0.03f; + const glm::vec4 LOOK_AT_TARGET_COLOR = { 0.8f, 0.0f, 0.0f, 0.75f }; + geometryCache->renderSphere(batch, LOOK_AT_TARGET_RADIUS, 15, 15, LOOK_AT_TARGET_COLOR, true); +} diff --git a/interface/src/avatar/Head.h b/interface/src/avatar/Head.h index a1c70f9dff..dff4702246 100644 --- a/interface/src/avatar/Head.h +++ b/interface/src/avatar/Head.h @@ -39,6 +39,7 @@ public: void setAverageLoudness(float averageLoudness) { _averageLoudness = averageLoudness; } void setReturnToCenter (bool returnHeadToCenter) { _returnHeadToCenter = returnHeadToCenter; } void setRenderLookatVectors(bool onOff) { _renderLookatVectors = onOff; } + void setRenderLookatTarget(bool onOff) { _renderLookatTarget = onOff; } /// \return orientationBase+Delta glm::quat getFinalOrientationInLocalFrame() const; @@ -123,6 +124,7 @@ private: float _mouth3; float _mouth4; bool _renderLookatVectors; + bool _renderLookatTarget; glm::vec3 _saccade; glm::vec3 _saccadeTarget; float _leftEyeBlinkVelocity; @@ -151,6 +153,7 @@ private: // private methods void renderLookatVectors(RenderArgs* renderArgs, glm::vec3 leftEyePosition, glm::vec3 rightEyePosition, glm::vec3 lookatPosition); + void renderLookatTarget(RenderArgs* renderArgs, glm::vec3 lookatPosition); void calculateMouthShapes(); void applyEyelidOffset(glm::quat headOrientation); diff --git a/tests/ui/src/main.cpp b/tests/ui/src/main.cpp index e8ab7e02df..2de2e1e6c6 100644 --- a/tests/ui/src/main.cpp +++ b/tests/ui/src/main.cpp @@ -177,6 +177,7 @@ public: RenderFocusIndicator, RenderHeadCollisionShapes, RenderLookAtVectors, + RenderLookAtTargets, RenderSkeletonCollisionShapes, RenderTargetFramerate, RenderTargetFramerateUnlimited, From 3be2ddf234b775178bc9d2ee2669b88e3c816d2c Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 5 Aug 2015 12:05:04 -0700 Subject: [PATCH 20/53] Fix calibration menu item actions --- interface/src/Menu.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index c124f2caec..f1d03d4e78 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -445,9 +445,9 @@ Menu::Menu() { addActionToQMenuAndActionHash(calibrateEyeTrackingMenu, MenuOption::OnePointCalibration, 0, qApp, SLOT(calibrateEyeTracker1Point())); addActionToQMenuAndActionHash(calibrateEyeTrackingMenu, MenuOption::ThreePointCalibration, 0, - qApp, SLOT(calibrateEyeTracker3Point())); + qApp, SLOT(calibrateEyeTracker3Points())); addActionToQMenuAndActionHash(calibrateEyeTrackingMenu, MenuOption::FivePointCalibration, 0, - qApp, SLOT(calibrateEyeTracker5Point())); + qApp, SLOT(calibrateEyeTracker5Points())); } addCheckableActionToQMenuAndActionHash(eyeTrackingMenu, MenuOption::SimulateEyeTracking, 0, false, qApp, SLOT(setActiveEyeTracker())); From a15943d941a9bc87443fc6b0677559825f068db5 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 5 Aug 2015 13:39:26 -0700 Subject: [PATCH 21/53] Increase size of look-at target sphere --- interface/src/avatar/Head.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp index aa7084b8e0..46f38acb33 100644 --- a/interface/src/avatar/Head.cpp +++ b/interface/src/avatar/Head.cpp @@ -428,7 +428,7 @@ void Head::renderLookatTarget(RenderArgs* renderArgs, glm::vec3 lookatPosition) deferredLighting->bindSimpleProgram(batch); auto geometryCache = DependencyManager::get(); - const float LOOK_AT_TARGET_RADIUS = 0.03f; + const float LOOK_AT_TARGET_RADIUS = 0.075f; const glm::vec4 LOOK_AT_TARGET_COLOR = { 0.8f, 0.0f, 0.0f, 0.75f }; geometryCache->renderSphere(batch, LOOK_AT_TARGET_RADIUS, 15, 15, LOOK_AT_TARGET_COLOR, true); } From e8eac7db3432a56d131a645b35eb8428c38d98a5 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 5 Aug 2015 13:40:57 -0700 Subject: [PATCH 22/53] Render look-at vectors and target in first person view --- interface/src/avatar/Avatar.cpp | 2 ++ interface/src/avatar/Head.cpp | 3 +++ interface/src/avatar/Head.h | 1 + interface/src/avatar/MyAvatar.cpp | 1 + 4 files changed, 7 insertions(+) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index abb671f207..ecae0b017e 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -603,7 +603,9 @@ void Avatar::renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, floa getHand()->render(renderArgs, false); } + getHead()->render(renderArgs, 1.0f, renderFrustum); + getHead()->renderLookAts(renderArgs); } bool Avatar::shouldRenderHead(const RenderArgs* renderArgs) const { diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp index 46f38acb33..bdd53cfd7f 100644 --- a/interface/src/avatar/Head.cpp +++ b/interface/src/avatar/Head.cpp @@ -303,6 +303,9 @@ void Head::relaxLean(float deltaTime) { } void Head::render(RenderArgs* renderArgs, float alpha, ViewFrustum* renderFrustum) { +} + +void Head::renderLookAts(RenderArgs* renderArgs) { if (_renderLookatVectors) { renderLookatVectors(renderArgs, _leftEyePosition, _rightEyePosition, getCorrectedLookAtPosition()); } diff --git a/interface/src/avatar/Head.h b/interface/src/avatar/Head.h index dff4702246..43042422d5 100644 --- a/interface/src/avatar/Head.h +++ b/interface/src/avatar/Head.h @@ -40,6 +40,7 @@ public: void setReturnToCenter (bool returnHeadToCenter) { _returnHeadToCenter = returnHeadToCenter; } void setRenderLookatVectors(bool onOff) { _renderLookatVectors = onOff; } void setRenderLookatTarget(bool onOff) { _renderLookatTarget = onOff; } + void renderLookAts(RenderArgs* renderArgs); /// \return orientationBase+Delta glm::quat getFinalOrientationInLocalFrame() const; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 2d7cf4ca5e..b1c2d113f9 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1160,6 +1160,7 @@ void MyAvatar::renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, fl if (shouldRenderHead(renderArgs)) { getHead()->render(renderArgs, 1.0f, renderFrustum); } + getHead()->renderLookAts(renderArgs); getHand()->render(renderArgs, true); } From 551c00dc23eea3722712f9863aeb03b987c76982 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 6 Aug 2015 16:41:06 -0700 Subject: [PATCH 23/53] Fix look-at position calculation when in HMD display Calculate look-at target from camera position and orientation. Render own look-at vectors from camera eye positions. Draw other avatar's look-at vectors from their eyeballs. With and without eye trackers. --- interface/src/Application.cpp | 20 +++++++++++++++----- interface/src/avatar/Head.cpp | 4 ++-- interface/src/avatar/Head.h | 2 +- interface/src/avatar/MyAvatar.cpp | 10 +++++++++- 4 files changed, 27 insertions(+), 9 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index c3d810fb0b..992687d114 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2347,10 +2347,15 @@ void Application::updateMyAvatarLookAtPosition() { } else { lookAtSpot = _myCamera.getPosition() + OculusManager::getMidEyePosition(); } - } else if (eyeTracker->isTracking() && (isHMDMode() || eyeTracker->isSimulating())) { + } else if (eyeTracker->isTracking() && (OculusManager::isConnected() || eyeTracker->isSimulating())) { // Look at the point that the user is looking at. - lookAtSpot = _myAvatar->getHead()->getEyePosition() + - (_myAvatar->getHead()->getFinalOrientationInWorldFrame() * eyeTracker->getLookAtPosition()); + if (OculusManager::isConnected()) { + lookAtSpot = _myCamera.getPosition() + OculusManager::getMidEyePosition() + + _myAvatar->getOrientation() * (OculusManager::getOrientation() * eyeTracker->getLookAtPosition()); + } else { + lookAtSpot = _myAvatar->getHead()->getEyePosition() + + (_myAvatar->getHead()->getFinalOrientationInWorldFrame() * eyeTracker->getLookAtPosition()); + } } else { AvatarSharedPointer lookingAt = _myAvatar->getLookAtTargetAvatar().lock(); if (lookingAt && _myAvatar != lookingAt.get()) { @@ -2383,8 +2388,13 @@ void Application::updateMyAvatarLookAtPosition() { } } else { // I am not looking at anyone else, so just look forward - lookAtSpot = _myAvatar->getHead()->getEyePosition() + - (_myAvatar->getHead()->getFinalOrientationInWorldFrame() * glm::vec3(0.0f, 0.0f, -TREE_SCALE)); + if (OculusManager::isConnected()) { + lookAtSpot = _myCamera.getPosition() + OculusManager::getMidEyePosition() + + _myAvatar->getOrientation() * (OculusManager::getOrientation() * glm::vec3(0.0f, 0.0f, -TREE_SCALE)); + } else { + lookAtSpot = _myAvatar->getHead()->getEyePosition() + + (_myAvatar->getHead()->getFinalOrientationInWorldFrame() * glm::vec3(0.0f, 0.0f, -TREE_SCALE)); + } } // Deflect the eyes a bit to match the detected gaze from the face tracker if active. diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp index a993fddf1c..b9cdb174d3 100644 --- a/interface/src/avatar/Head.cpp +++ b/interface/src/avatar/Head.cpp @@ -305,9 +305,9 @@ void Head::relaxLean(float deltaTime) { void Head::render(RenderArgs* renderArgs, float alpha, ViewFrustum* renderFrustum) { } -void Head::renderLookAts(RenderArgs* renderArgs) { +void Head::renderLookAts(RenderArgs* renderArgs, glm::vec3 eyeOffset) { if (_renderLookatVectors) { - renderLookatVectors(renderArgs, _leftEyePosition, _rightEyePosition, getCorrectedLookAtPosition()); + renderLookatVectors(renderArgs, _leftEyePosition + eyeOffset, _rightEyePosition + eyeOffset, getCorrectedLookAtPosition()); } if (_renderLookatTarget) { renderLookatTarget(renderArgs, getCorrectedLookAtPosition()); diff --git a/interface/src/avatar/Head.h b/interface/src/avatar/Head.h index 43042422d5..8beaf26f36 100644 --- a/interface/src/avatar/Head.h +++ b/interface/src/avatar/Head.h @@ -40,7 +40,7 @@ public: void setReturnToCenter (bool returnHeadToCenter) { _returnHeadToCenter = returnHeadToCenter; } void setRenderLookatVectors(bool onOff) { _renderLookatVectors = onOff; } void setRenderLookatTarget(bool onOff) { _renderLookatTarget = onOff; } - void renderLookAts(RenderArgs* renderArgs); + void renderLookAts(RenderArgs* renderArgs, glm::vec3 eyeOffset = glm::vec3()); /// \return orientationBase+Delta glm::quat getFinalOrientationInLocalFrame() const; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 629c873323..7c8fd0dfce 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1166,7 +1166,15 @@ void MyAvatar::renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, fl if (shouldRenderHead(renderArgs)) { getHead()->render(renderArgs, 1.0f, renderFrustum); } - getHead()->renderLookAts(renderArgs); + + if (qApp->isHMDMode()) { + glm::vec3 eyeOffset = + OculusManager::getMidEyePosition() + Application::getInstance()->getCamera()->getPosition() - getEyePosition(); + getHead()->renderLookAts(renderArgs, eyeOffset); + } else { + getHead()->renderLookAts(renderArgs); + } + getHand()->render(renderArgs, true); } From 427b8b973c9ea5d8a8c77131a427302e82b06bec Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 6 Aug 2015 17:20:03 -0700 Subject: [PATCH 24/53] Fix excluding saccades when doing eye tracking --- interface/src/avatar/Head.cpp | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp index b9cdb174d3..d6aedc5e20 100644 --- a/interface/src/avatar/Head.cpp +++ b/interface/src/avatar/Head.cpp @@ -131,20 +131,25 @@ void Head::simulate(float deltaTime, bool isMine, bool billboard) { setTorsoTwist(currentTwist + (getFinalYaw() * BODY_FOLLOW_HEAD_FACTOR - currentTwist) * BODY_FOLLOW_HEAD_YAW_RATE); } - if (!(_isFaceTrackerConnected || _isEyeTrackerConnected || billboard)) { - // Update eye saccades - const float AVERAGE_MICROSACCADE_INTERVAL = 1.0f; - const float AVERAGE_SACCADE_INTERVAL = 6.0f; - const float MICROSACCADE_MAGNITUDE = 0.002f; - const float SACCADE_MAGNITUDE = 0.04f; - const float NOMINAL_FRAME_RATE = 60.0f; + if (!(_isFaceTrackerConnected || billboard)) { - if (randFloat() < deltaTime / AVERAGE_MICROSACCADE_INTERVAL) { - _saccadeTarget = MICROSACCADE_MAGNITUDE * randVector(); - } else if (randFloat() < deltaTime / AVERAGE_SACCADE_INTERVAL) { - _saccadeTarget = SACCADE_MAGNITUDE * randVector(); + if (!_isEyeTrackerConnected) { + // Update eye saccades + const float AVERAGE_MICROSACCADE_INTERVAL = 1.0f; + const float AVERAGE_SACCADE_INTERVAL = 6.0f; + const float MICROSACCADE_MAGNITUDE = 0.002f; + const float SACCADE_MAGNITUDE = 0.04f; + const float NOMINAL_FRAME_RATE = 60.0f; + + if (randFloat() < deltaTime / AVERAGE_MICROSACCADE_INTERVAL) { + _saccadeTarget = MICROSACCADE_MAGNITUDE * randVector(); + } else if (randFloat() < deltaTime / AVERAGE_SACCADE_INTERVAL) { + _saccadeTarget = SACCADE_MAGNITUDE * randVector(); + } + _saccade += (_saccadeTarget - _saccade) * pow(0.5f, NOMINAL_FRAME_RATE * deltaTime); + } else { + _saccade = glm::vec3(); } - _saccade += (_saccadeTarget - _saccade) * pow(0.5f, NOMINAL_FRAME_RATE * deltaTime); // Detect transition from talking to not; force blink after that and a delay bool forceBlink = false; From 738c5658fa403b64d642325696b2f0aac3efff26 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 6 Aug 2015 20:46:51 -0700 Subject: [PATCH 25/53] Guard against NaNs in eye tracker look-at position --- interface/src/devices/EyeTracker.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/interface/src/devices/EyeTracker.cpp b/interface/src/devices/EyeTracker.cpp index 0e24dde8ee..c8942a889b 100644 --- a/interface/src/devices/EyeTracker.cpp +++ b/interface/src/devices/EyeTracker.cpp @@ -76,20 +76,27 @@ void EyeTracker::processData(smi_CallbackDataStruct* data) { float rightLinePlaneDotProduct = glm::dot(rightLineDirection, planeNormal); // Gaze into distance if eyes are parallel or diverged; otherwise the look-at is the average of look-at points + glm::vec3 lookAtPosition; if (abs(leftLinePlaneDotProduct) <= FLT_EPSILON || abs(rightLinePlaneDotProduct) <= FLT_EPSILON) { - _lookAtPosition = monocularDirection * (float)TREE_SCALE; + lookAtPosition = monocularDirection * (float)TREE_SCALE; } else { float leftDistance = glm::dot(planePoint - leftLinePoint, planeNormal) / leftLinePlaneDotProduct; float rightDistance = glm::dot(planePoint - rightLinePoint, planeNormal) / rightLinePlaneDotProduct; if (leftDistance <= 0.0f || rightDistance <= 0.0f || leftDistance > (float)TREE_SCALE || rightDistance > (float)TREE_SCALE) { - _lookAtPosition = monocularDirection * (float)TREE_SCALE; + lookAtPosition = monocularDirection * (float)TREE_SCALE; } else { glm::vec3 leftIntersectionPoint = leftLinePoint + leftDistance * leftLineDirection; glm::vec3 rightIntersectionPoint = rightLinePoint + rightDistance * rightLineDirection; - _lookAtPosition = (leftIntersectionPoint + rightIntersectionPoint) / 2.0f; + lookAtPosition = (leftIntersectionPoint + rightIntersectionPoint) / 2.0f; } } + + if (glm::isnan(lookAtPosition.x) || glm::isnan(lookAtPosition.y) || glm::isnan(lookAtPosition.z)) { + return; + } + + _lookAtPosition = lookAtPosition; } } #endif From e37fd956eda288d464ab827ba40c7fbb8153a25b Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 6 Aug 2015 22:51:25 -0700 Subject: [PATCH 26/53] Fix look-at target position getting stuck when avatar moves out of range --- interface/src/avatar/MyAvatar.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 7c8fd0dfce..848838a4e5 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -859,6 +859,8 @@ void MyAvatar::updateLookAtTargetAvatar() { } else { avatar->getHead()->clearCorrectedLookAtPosition(); } + } else { + avatar->getHead()->clearCorrectedLookAtPosition(); } } auto avatarPointer = _lookAtTargetAvatar.lock(); From e2bf5cad3e474104b6a7e92a55f4318314659bce Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 7 Aug 2015 11:59:11 -0700 Subject: [PATCH 27/53] Resume eye tracking at start-up if was running at shut-down --- interface/src/Application.cpp | 6 +++++- interface/src/devices/EyeTracker.h | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 992687d114..c9047e527f 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2042,9 +2042,13 @@ void Application::setActiveFaceTracker() { void Application::setActiveEyeTracker() { #ifdef HAVE_IVIEWHMD + auto eyeTracker = DependencyManager::get(); + if (!eyeTracker->isInitialized()) { + return; + } + bool isEyeTracking = Menu::getInstance()->isOptionChecked(MenuOption::SMIEyeTracking); bool isSimulating = Menu::getInstance()->isOptionChecked(MenuOption::SimulateEyeTracking); - auto eyeTracker = DependencyManager::get(); eyeTracker->setEnabled(isEyeTracking, isSimulating); if (isEyeTracking && !eyeTracker->isTracking()) { Menu::getInstance()->setIsOptionChecked(MenuOption::SMIEyeTracking, false); diff --git a/interface/src/devices/EyeTracker.h b/interface/src/devices/EyeTracker.h index 3e602bb12d..8747076340 100644 --- a/interface/src/devices/EyeTracker.h +++ b/interface/src/devices/EyeTracker.h @@ -33,6 +33,7 @@ public: void setEnabled(bool enabled, bool simulate); void reset(); + bool isInitialized() const { return _isInitialized; } bool isTracking() const { return _isEnabled; } bool isSimulating() const { return _isSimulating; } From ea03067a02515ab967ff6db502caae0b96f4e461 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 7 Aug 2015 12:23:43 -0700 Subject: [PATCH 28/53] When eye tracking data is lot for > N seconds handle as "not tracking" Revert to default look-at behaviour and saccades until tracking resumes. --- interface/src/Application.cpp | 2 +- interface/src/devices/EyeTracker.cpp | 9 +++++++++ interface/src/devices/EyeTracker.h | 5 ++++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index c9047e527f..56a33695bc 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2050,7 +2050,7 @@ void Application::setActiveEyeTracker() { bool isEyeTracking = Menu::getInstance()->isOptionChecked(MenuOption::SMIEyeTracking); bool isSimulating = Menu::getInstance()->isOptionChecked(MenuOption::SimulateEyeTracking); eyeTracker->setEnabled(isEyeTracking, isSimulating); - if (isEyeTracking && !eyeTracker->isTracking()) { + if (isEyeTracking && !eyeTracker->isEnabled()) { Menu::getInstance()->setIsOptionChecked(MenuOption::SMIEyeTracking, false); isEyeTracking = false; } diff --git a/interface/src/devices/EyeTracker.cpp b/interface/src/devices/EyeTracker.cpp index c8942a889b..9a35448bd0 100644 --- a/interface/src/devices/EyeTracker.cpp +++ b/interface/src/devices/EyeTracker.cpp @@ -13,6 +13,8 @@ #include +#include + #include "InterfaceLogging.h" #include "OctreeConstants.h" @@ -36,6 +38,8 @@ EyeTracker::~EyeTracker() { #ifdef HAVE_IVIEWHMD void EyeTracker::processData(smi_CallbackDataStruct* data) { + _lastProcessDataTimestamp = usecTimestampNow(); + if (!_isEnabled) { return; } @@ -150,6 +154,11 @@ void EyeTracker::reset() { // Nothing to do. } +bool EyeTracker::isTracking() const { + static const quint64 ACTIVE_TIMEOUT_USECS = 2000000; // 2 secs + return (usecTimestampNow() - _lastProcessDataTimestamp < ACTIVE_TIMEOUT_USECS); +} + #ifdef HAVE_IVIEWHMD void EyeTracker::calibrate(int points) { smi_CalibrationHMDStruct* calibrationHMDStruct; diff --git a/interface/src/devices/EyeTracker.h b/interface/src/devices/EyeTracker.h index 8747076340..4976c14d08 100644 --- a/interface/src/devices/EyeTracker.h +++ b/interface/src/devices/EyeTracker.h @@ -34,7 +34,8 @@ public: void reset(); bool isInitialized() const { return _isInitialized; } - bool isTracking() const { return _isEnabled; } + bool isEnabled() const { return _isEnabled; } + bool isTracking() const; bool isSimulating() const { return _isSimulating; } glm::vec3 getLookAtPosition() const { return _lookAtPosition; } // From mid eye point in head frame. @@ -52,6 +53,8 @@ private: bool _isEnabled = false; bool _isSimulating = false; + quint64 _lastProcessDataTimestamp; + glm::vec3 _lookAtPosition; }; From b54c2526c19b28009860de608957b502f6ea0d11 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 7 Aug 2015 16:13:25 -0700 Subject: [PATCH 29/53] Fix rendering own look-at vectors in HMD mode Vectors now originate at user's physical eyeballs rather than at user avatar's eye separation. --- interface/src/avatar/Head.cpp | 8 ++++++-- interface/src/avatar/Head.h | 3 ++- interface/src/avatar/MyAvatar.cpp | 6 +++--- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp index d6aedc5e20..0ae395f61a 100644 --- a/interface/src/avatar/Head.cpp +++ b/interface/src/avatar/Head.cpp @@ -310,9 +310,13 @@ void Head::relaxLean(float deltaTime) { void Head::render(RenderArgs* renderArgs, float alpha, ViewFrustum* renderFrustum) { } -void Head::renderLookAts(RenderArgs* renderArgs, glm::vec3 eyeOffset) { +void Head::renderLookAts(RenderArgs* renderArgs) { + renderLookAts(renderArgs, _leftEyePosition, _rightEyePosition); +} + +void Head::renderLookAts(RenderArgs* renderArgs, glm::vec3 leftEyePosition, glm::vec3 rightEyePosition) { if (_renderLookatVectors) { - renderLookatVectors(renderArgs, _leftEyePosition + eyeOffset, _rightEyePosition + eyeOffset, getCorrectedLookAtPosition()); + renderLookatVectors(renderArgs, leftEyePosition, rightEyePosition, getCorrectedLookAtPosition()); } if (_renderLookatTarget) { renderLookatTarget(renderArgs, getCorrectedLookAtPosition()); diff --git a/interface/src/avatar/Head.h b/interface/src/avatar/Head.h index 8beaf26f36..691775b029 100644 --- a/interface/src/avatar/Head.h +++ b/interface/src/avatar/Head.h @@ -40,7 +40,8 @@ public: void setReturnToCenter (bool returnHeadToCenter) { _returnHeadToCenter = returnHeadToCenter; } void setRenderLookatVectors(bool onOff) { _renderLookatVectors = onOff; } void setRenderLookatTarget(bool onOff) { _renderLookatTarget = onOff; } - void renderLookAts(RenderArgs* renderArgs, glm::vec3 eyeOffset = glm::vec3()); + void renderLookAts(RenderArgs* renderArgs); + void renderLookAts(RenderArgs* renderArgs, glm::vec3 leftEyePosition, glm::vec3 rightEyePosition); /// \return orientationBase+Delta glm::quat getFinalOrientationInLocalFrame() const; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 3578acaa60..59eb899f54 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1167,9 +1167,9 @@ void MyAvatar::renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, fl } if (qApp->isHMDMode()) { - glm::vec3 eyeOffset = - OculusManager::getMidEyePosition() + Application::getInstance()->getCamera()->getPosition() - getEyePosition(); - getHead()->renderLookAts(renderArgs, eyeOffset); + glm::vec3 cameraPosition = Application::getInstance()->getCamera()->getPosition(); + getHead()->renderLookAts(renderArgs, cameraPosition + OculusManager::getLeftEyePosition(), + cameraPosition + OculusManager::getRightEyePosition()); } else { getHead()->renderLookAts(renderArgs); } From 4f3c4a531227fec3ea77e5ee6978e676240f53e0 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 10 Aug 2015 12:52:47 -0700 Subject: [PATCH 30/53] Tidy up handling of eye tracker streaming --- interface/src/devices/EyeTracker.cpp | 14 +++++++++----- interface/src/devices/EyeTracker.h | 1 + 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/interface/src/devices/EyeTracker.cpp b/interface/src/devices/EyeTracker.cpp index 9a35448bd0..7620dc070f 100644 --- a/interface/src/devices/EyeTracker.cpp +++ b/interface/src/devices/EyeTracker.cpp @@ -29,9 +29,11 @@ static void CALLBACK eyeTrackerCallback(smi_CallbackDataStruct* data) { EyeTracker::~EyeTracker() { #ifdef HAVE_IVIEWHMD - int result = smi_quit(); - if (result != SMI_RET_SUCCESS) { - qCWarning(interfaceapp) << "Eye Tracker: Error terminating tracking:" << smiReturnValueToString(result); + if (_isStreaming) { + int result = smi_quit(); + if (result != SMI_RET_SUCCESS) { + qCWarning(interfaceapp) << "Eye Tracker: Error terminating tracking:" << smiReturnValueToString(result); + } } #endif } @@ -131,16 +133,18 @@ void EyeTracker::setEnabled(bool enabled, bool simulate) { qCDebug(interfaceapp) << "Eye Tracker: Set enabled =" << enabled << ", simulate =" << simulate; bool success = true; int result = 0; - if (enabled) { + if (enabled && !_isStreaming) { // There is no smi_stopStreaming() method so keep streaming once started in case tracking is re-enabled after stopping. result = smi_startStreaming(simulate); if (result != SMI_RET_SUCCESS) { qCWarning(interfaceapp) << "Eye Tracker: Error starting streaming:" << smiReturnValueToString(result); success = false; } + + _isStreaming = success; } - _isEnabled = enabled && success; + _isEnabled = enabled && _isStreaming; _isSimulating = _isEnabled && simulate; if (!success && result != SMI_ERROR_HMD_NOT_SUPPORTED) { diff --git a/interface/src/devices/EyeTracker.h b/interface/src/devices/EyeTracker.h index 4976c14d08..b1c71c0303 100644 --- a/interface/src/devices/EyeTracker.h +++ b/interface/src/devices/EyeTracker.h @@ -50,6 +50,7 @@ private: QString smiReturnValueToString(int value); bool _isInitialized = false; + bool _isStreaming = false; bool _isEnabled = false; bool _isSimulating = false; From 658434e02091e2674ca1b2278e48a5af10f703b2 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 10 Aug 2015 13:20:42 -0700 Subject: [PATCH 31/53] Automatically save and restore eye tracker calibration Automatically saves and restores just a single calibration so that user doesn't have to manually save or load, which isn't very convenient to do with an HMD on. --- interface/src/devices/EyeTracker.cpp | 42 +++++++++++++++++++++------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/interface/src/devices/EyeTracker.cpp b/interface/src/devices/EyeTracker.cpp index 7620dc070f..5f89ca0732 100644 --- a/interface/src/devices/EyeTracker.cpp +++ b/interface/src/devices/EyeTracker.cpp @@ -18,6 +18,10 @@ #include "InterfaceLogging.h" #include "OctreeConstants.h" +#ifdef HAVE_IVIEWHMD +char* HIGH_FIDELITY_EYE_TRACKER_CALIBRATION = "HighFidelityEyeTrackerCalibration"; +#endif + #ifdef HAVE_IVIEWHMD static void CALLBACK eyeTrackerCallback(smi_CallbackDataStruct* data) { auto eyeTracker = DependencyManager::get(); @@ -131,26 +135,35 @@ void EyeTracker::setEnabled(bool enabled, bool simulate) { #ifdef HAVE_IVIEWHMD qCDebug(interfaceapp) << "Eye Tracker: Set enabled =" << enabled << ", simulate =" << simulate; - bool success = true; - int result = 0; if (enabled && !_isStreaming) { // There is no smi_stopStreaming() method so keep streaming once started in case tracking is re-enabled after stopping. - result = smi_startStreaming(simulate); + int result = smi_startStreaming(simulate); if (result != SMI_RET_SUCCESS) { qCWarning(interfaceapp) << "Eye Tracker: Error starting streaming:" << smiReturnValueToString(result); - success = false; + // Display error dialog except if SMI SDK has already displayed an error message. + if (result != SMI_ERROR_HMD_NOT_SUPPORTED) { + QMessageBox::warning(nullptr, "Eye Tracker Error", smiReturnValueToString(result)); + } } - _isStreaming = success; + _isStreaming = (result == SMI_RET_SUCCESS); + + if (_isStreaming) { + // Automatically load calibration if one has been saved. + QString availableCalibrations = QString(smi_getAvailableCalibrations()); + if (availableCalibrations.contains(HIGH_FIDELITY_EYE_TRACKER_CALIBRATION)) { + result = smi_loadCalibration(HIGH_FIDELITY_EYE_TRACKER_CALIBRATION); + if (result != SMI_RET_SUCCESS) { + qCWarning(interfaceapp) << "Eye Tracker: Error loading calibration:" << smiReturnValueToString(result); + QMessageBox::warning(nullptr, "Eye Tracker Error", "Error loading calibration" + + smiReturnValueToString(result)); + } + } + } } _isEnabled = enabled && _isStreaming; _isSimulating = _isEnabled && simulate; - - if (!success && result != SMI_ERROR_HMD_NOT_SUPPORTED) { - // Display error dialog after updating _isEnabled. Except if SMI SDK has already displayed an error message. - QMessageBox::warning(nullptr, "Eye Tracker Error", smiReturnValueToString(result)); - } #endif } @@ -199,6 +212,11 @@ void EyeTracker::calibrate(int points) { result = smi_calibrate(); if (result != SMI_RET_SUCCESS) { qCWarning(interfaceapp) << "Eye Tracker: Error performing calibration:" << smiReturnValueToString(result); + } else { + result = smi_saveCalibration(HIGH_FIDELITY_EYE_TRACKER_CALIBRATION); + if (result != SMI_RET_SUCCESS) { + qCWarning(interfaceapp) << "Eye Tracker: Error saving calibration:" << smiReturnValueToString(result); + } } } @@ -226,6 +244,10 @@ QString EyeTracker::smiReturnValueToString(int value) { return "Eye cameras not available"; case smi_ErrorReturnValue::SMI_ERROR_OCULUS_RUNTIME_NOT_SUPPORTED: return "Oculus runtime not supported"; + case smi_ErrorReturnValue::SMI_ERROR_FILE_NOT_FOUND: + return "File not found"; + case smi_ErrorReturnValue::SMI_ERROR_FILE_EMPTY: + return "File empty"; case smi_ErrorReturnValue::SMI_ERROR_UNKNOWN: return "Unknown error"; default: From e7beb544f6b03421daa6c9ccb9b8b7ff192a3651 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 10 Aug 2015 13:22:46 -0700 Subject: [PATCH 32/53] Log successfully loading eye tracker calibration --- interface/src/devices/EyeTracker.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/interface/src/devices/EyeTracker.cpp b/interface/src/devices/EyeTracker.cpp index 5f89ca0732..ee688b1f38 100644 --- a/interface/src/devices/EyeTracker.cpp +++ b/interface/src/devices/EyeTracker.cpp @@ -157,6 +157,8 @@ void EyeTracker::setEnabled(bool enabled, bool simulate) { qCWarning(interfaceapp) << "Eye Tracker: Error loading calibration:" << smiReturnValueToString(result); QMessageBox::warning(nullptr, "Eye Tracker Error", "Error loading calibration" + smiReturnValueToString(result)); + } else { + qCDebug(interfaceapp) << "Eye Tracker: Loaded calibration"; } } } From 81d8edae5f24b2fd60aa68b19cd79d0c3e2a7654 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 10 Aug 2015 13:23:14 -0700 Subject: [PATCH 33/53] Add missing 5 point calibration --- interface/src/devices/EyeTracker.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/interface/src/devices/EyeTracker.cpp b/interface/src/devices/EyeTracker.cpp index ee688b1f38..41ca6edccc 100644 --- a/interface/src/devices/EyeTracker.cpp +++ b/interface/src/devices/EyeTracker.cpp @@ -193,6 +193,10 @@ void EyeTracker::calibrate(int points) { calibrationType = SMI_THREE_POINT_CALIBRATION; qCDebug(interfaceapp) << "Eye Tracker: Three point calibration"; break; + case 5: + calibrationType = SMI_FIVE_POINT_CALIBRATION; + qCDebug(interfaceapp) << "Eye Tracker: Five point calibration"; + break; default: qCWarning(interfaceapp) << "Eye Tracker: Invalid calibration specified"; return; From 8b326414accae284879c1eaa13f12f36a99034aa Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 10 Aug 2015 19:19:29 -0700 Subject: [PATCH 34/53] Start eye tracker streaming such that don't block Interface Start method is called in a separate thread. --- interface/src/Application.cpp | 5 +- interface/src/devices/EyeTracker.cpp | 94 +++++++++++++++++++--------- interface/src/devices/EyeTracker.h | 11 +++- 3 files changed, 77 insertions(+), 33 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 2fd18a4fe2..cd919da2cc 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2050,10 +2050,7 @@ void Application::setActiveEyeTracker() { bool isEyeTracking = Menu::getInstance()->isOptionChecked(MenuOption::SMIEyeTracking); bool isSimulating = Menu::getInstance()->isOptionChecked(MenuOption::SimulateEyeTracking); eyeTracker->setEnabled(isEyeTracking, isSimulating); - if (isEyeTracking && !eyeTracker->isEnabled()) { - Menu::getInstance()->setIsOptionChecked(MenuOption::SMIEyeTracking, false); - isEyeTracking = false; - } + Menu::getInstance()->getActionForOption(MenuOption::OnePointCalibration)->setEnabled(isEyeTracking && !isSimulating); Menu::getInstance()->getActionForOption(MenuOption::ThreePointCalibration)->setEnabled(isEyeTracking && !isSimulating); Menu::getInstance()->getActionForOption(MenuOption::FivePointCalibration)->setEnabled(isEyeTracking && !isSimulating); diff --git a/interface/src/devices/EyeTracker.cpp b/interface/src/devices/EyeTracker.cpp index 41ca6edccc..b513bd7ac7 100644 --- a/interface/src/devices/EyeTracker.cpp +++ b/interface/src/devices/EyeTracker.cpp @@ -11,7 +11,9 @@ #include "EyeTracker.h" +#include #include +#include #include @@ -125,9 +127,47 @@ void EyeTracker::init() { } else { _isInitialized = true; } + + connect(&_startStreamingWatcher, SIGNAL(finished()), this, SLOT(onStreamStarted())); #endif } +#ifdef HAVE_IVIEWHMD +int EyeTracker::startStreaming(bool simulate) { + return smi_startStreaming(simulate); // This call blocks execution. +} +#endif + +#ifdef HAVE_IVIEWHMD +void EyeTracker::onStreamStarted() { + int result = _startStreamingWatcher.result(); + _isStreaming = (result == SMI_RET_SUCCESS); + + if (result != SMI_RET_SUCCESS) { + qCWarning(interfaceapp) << "Eye Tracker: Error starting streaming:" << smiReturnValueToString(result); + // Display error dialog unless SMI SDK has already displayed an error message. + if (result != SMI_ERROR_HMD_NOT_SUPPORTED) { + QMessageBox::warning(nullptr, "Eye Tracker Error", smiReturnValueToString(result)); + } + } + + if (_isStreaming) { + // Automatically load calibration if one has been saved. + QString availableCalibrations = QString(smi_getAvailableCalibrations()); + if (availableCalibrations.contains(HIGH_FIDELITY_EYE_TRACKER_CALIBRATION)) { + result = smi_loadCalibration(HIGH_FIDELITY_EYE_TRACKER_CALIBRATION); + if (result != SMI_RET_SUCCESS) { + qCWarning(interfaceapp) << "Eye Tracker: Error loading calibration:" << smiReturnValueToString(result); + QMessageBox::warning(nullptr, "Eye Tracker Error", "Error loading calibration" + + smiReturnValueToString(result)); + } else { + qCDebug(interfaceapp) << "Eye Tracker: Loaded calibration"; + } + } + } +} +#endif + void EyeTracker::setEnabled(bool enabled, bool simulate) { if (!_isInitialized) { return; @@ -135,37 +175,29 @@ void EyeTracker::setEnabled(bool enabled, bool simulate) { #ifdef HAVE_IVIEWHMD qCDebug(interfaceapp) << "Eye Tracker: Set enabled =" << enabled << ", simulate =" << simulate; - if (enabled && !_isStreaming) { - // There is no smi_stopStreaming() method so keep streaming once started in case tracking is re-enabled after stopping. - int result = smi_startStreaming(simulate); + + // There is no smi_stopStreaming() method and after an smi_quit(), streaming cannot be restarted (at least not for + // simulated data). So keep streaming once started in case tracking is re-enabled after stopping. + + // Try to stop streaming if changing whether simulating or not. + if (enabled && _isStreaming && _isStreamSimulating != simulate) { + int result = smi_quit(); if (result != SMI_RET_SUCCESS) { - qCWarning(interfaceapp) << "Eye Tracker: Error starting streaming:" << smiReturnValueToString(result); - // Display error dialog except if SMI SDK has already displayed an error message. - if (result != SMI_ERROR_HMD_NOT_SUPPORTED) { - QMessageBox::warning(nullptr, "Eye Tracker Error", smiReturnValueToString(result)); - } - } - - _isStreaming = (result == SMI_RET_SUCCESS); - - if (_isStreaming) { - // Automatically load calibration if one has been saved. - QString availableCalibrations = QString(smi_getAvailableCalibrations()); - if (availableCalibrations.contains(HIGH_FIDELITY_EYE_TRACKER_CALIBRATION)) { - result = smi_loadCalibration(HIGH_FIDELITY_EYE_TRACKER_CALIBRATION); - if (result != SMI_RET_SUCCESS) { - qCWarning(interfaceapp) << "Eye Tracker: Error loading calibration:" << smiReturnValueToString(result); - QMessageBox::warning(nullptr, "Eye Tracker Error", "Error loading calibration" - + smiReturnValueToString(result)); - } else { - qCDebug(interfaceapp) << "Eye Tracker: Loaded calibration"; - } - } + qCWarning(interfaceapp) << "Eye Tracker: Error stopping streaming:" << smiReturnValueToString(result); } + _isStreaming = false; } - _isEnabled = enabled && _isStreaming; - _isSimulating = _isEnabled && simulate; + if (enabled && !_isStreaming) { + // Start SMI streaming in a separate thread because it blocks. + QFuture future = QtConcurrent::run(this, &EyeTracker::startStreaming, simulate); + _startStreamingWatcher.setFuture(future); + _isStreamSimulating = simulate; + } + + _isEnabled = enabled; + _isSimulating = simulate; + #endif } @@ -175,11 +207,17 @@ void EyeTracker::reset() { bool EyeTracker::isTracking() const { static const quint64 ACTIVE_TIMEOUT_USECS = 2000000; // 2 secs - return (usecTimestampNow() - _lastProcessDataTimestamp < ACTIVE_TIMEOUT_USECS); + return _isEnabled && (usecTimestampNow() - _lastProcessDataTimestamp < ACTIVE_TIMEOUT_USECS); } #ifdef HAVE_IVIEWHMD void EyeTracker::calibrate(int points) { + + if (!_isStreaming) { + qCWarning(interfaceapp) << "Eye Tracker: Cannot calibrate because not streaming"; + return; + } + smi_CalibrationHMDStruct* calibrationHMDStruct; smi_createCalibrationHMDStruct(&calibrationHMDStruct); diff --git a/interface/src/devices/EyeTracker.h b/interface/src/devices/EyeTracker.h index b1c71c0303..0e760d9454 100644 --- a/interface/src/devices/EyeTracker.h +++ b/interface/src/devices/EyeTracker.h @@ -13,6 +13,7 @@ #define hifi_EyeTracker_h #include +#include #include @@ -44,19 +45,27 @@ public: void processData(smi_CallbackDataStruct* data); void calibrate(int points); + + int startStreaming(bool simulate); + +private slots: + void onStreamStarted(); #endif private: QString smiReturnValueToString(int value); bool _isInitialized = false; - bool _isStreaming = false; bool _isEnabled = false; bool _isSimulating = false; + bool _isStreaming = false; + bool _isStreamSimulating = false; quint64 _lastProcessDataTimestamp; glm::vec3 _lookAtPosition; + + QFutureWatcher _startStreamingWatcher; }; #endif // hifi_EyeTracker_h From f7dc5480b736e5e25abbddb7fde2d585c868ba54 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 10 Aug 2015 20:20:51 -0700 Subject: [PATCH 35/53] Fix #include path for OSX/Linux --- interface/src/devices/EyeTracker.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/devices/EyeTracker.cpp b/interface/src/devices/EyeTracker.cpp index b513bd7ac7..bea04c16a2 100644 --- a/interface/src/devices/EyeTracker.cpp +++ b/interface/src/devices/EyeTracker.cpp @@ -13,7 +13,7 @@ #include #include -#include +#include #include From 3e8d0ad712ad3e0993bc90b353c1931bf9142e59 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 11 Aug 2015 13:40:08 -0700 Subject: [PATCH 36/53] Fix to use new HMD methods --- interface/src/Application.cpp | 30 +++++++++++++++++++++++------- interface/src/Application.h | 3 ++- interface/src/avatar/MyAvatar.cpp | 14 ++++++++++++-- 3 files changed, 37 insertions(+), 10 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index fa85aa2192..23fc1f13e1 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2371,11 +2371,19 @@ void Application::updateMyAvatarLookAtPosition() { } else { lookAtSpot = _myCamera.getPosition() + transformPoint(_myAvatar->getSensorToWorldMatrix(), extractTranslation(getHMDSensorPose())); } - } else if (eyeTracker->isTracking() && (OculusManager::isConnected() || eyeTracker->isSimulating())) { + } else if (eyeTracker->isTracking() && (isHMDMode() || eyeTracker->isSimulating())) { // Look at the point that the user is looking at. - if (OculusManager::isConnected()) { - lookAtSpot = _myCamera.getPosition() + OculusManager::getMidEyePosition() + - _myAvatar->getOrientation() * (OculusManager::getOrientation() * eyeTracker->getLookAtPosition()); + if (isHMDMode()) { + + // TODO: Test ... + // Position of simulated look-at target should change with change in HMD position and orientation + + glm::mat4 headPose = getActiveDisplayPlugin()->getHeadPose(); + glm::quat hmdOrientation = glm::quat_cast(headPose); + glm::vec3 hmdPosition = glm::vec3(headPose[3]); + + lookAtSpot = _myCamera.getPosition() + hmdPosition + + _myAvatar->getOrientation() * (hmdOrientation * eyeTracker->getLookAtPosition()); } else { lookAtSpot = _myAvatar->getHead()->getEyePosition() + (_myAvatar->getHead()->getFinalOrientationInWorldFrame() * eyeTracker->getLookAtPosition()); @@ -2412,9 +2420,17 @@ void Application::updateMyAvatarLookAtPosition() { } } else { // I am not looking at anyone else, so just look forward - if (OculusManager::isConnected()) { - lookAtSpot = _myCamera.getPosition() + OculusManager::getMidEyePosition() + - _myAvatar->getOrientation() * (OculusManager::getOrientation() * glm::vec3(0.0f, 0.0f, -TREE_SCALE)); + if (isHMDMode()) { + + // TODO: Test ... + // Look-at vector should go into distance based on HMD position and orientation + + glm::mat4 headPose = getActiveDisplayPlugin()->getHeadPose(); + glm::quat hmdOrientation = glm::quat_cast(headPose); + glm::vec3 hmdPosition = glm::vec3(headPose[3]); + + lookAtSpot = _myCamera.getPosition() + hmdPosition + + _myAvatar->getOrientation() * (hmdOrientation * glm::vec3(0.0f, 0.0f, -TREE_SCALE)); } else { lookAtSpot = _myAvatar->getHead()->getEyePosition() + (_myAvatar->getHead()->getFinalOrientationInWorldFrame() * glm::vec3(0.0f, 0.0f, -TREE_SCALE)); diff --git a/interface/src/Application.h b/interface/src/Application.h index 0d308e7a8f..239b440822 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -292,9 +292,10 @@ public: virtual QGLWidget* getPrimarySurface() override; void setActiveDisplayPlugin(const QString& pluginName); -private: + DisplayPlugin * getActiveDisplayPlugin(); const DisplayPlugin * getActiveDisplayPlugin() const; + public: FileLogger* getLogger() { return _logger; } diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 18c29d49fe..99973f2256 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -1253,9 +1254,18 @@ void MyAvatar::renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, fl } if (qApp->isHMDMode()) { + + // TODO: Test ... + // Look-at vectors should go from HMD mid eyes regardless of combined position and orientation + + glm::mat4 leftEye = Application::getInstance()->getActiveDisplayPlugin()->getEyePose(Eye::Left); + glm::vec3 leftEyePosition = glm::vec3(leftEye[3]); + glm::mat4 rightEye = Application::getInstance()->getActiveDisplayPlugin()->getEyePose(Eye::Right); + glm::vec3 rightEyePosition = glm::vec3(leftEye[3]); + glm::vec3 cameraPosition = Application::getInstance()->getCamera()->getPosition(); - getHead()->renderLookAts(renderArgs, cameraPosition + OculusManager::getLeftEyePosition(), - cameraPosition + OculusManager::getRightEyePosition()); + getHead()->renderLookAts(renderArgs, cameraPosition + leftEyePosition, + cameraPosition + rightEyePosition); } else { getHead()->renderLookAts(renderArgs); } From bb205294b981d183b6bc86cd066340c152a43135 Mon Sep 17 00:00:00 2001 From: Seiji Emery Date: Tue, 11 Aug 2015 16:33:30 -0700 Subject: [PATCH 37/53] uiwidgets.js --- examples/games/solarsystem.js | 1012 +++++++++++++++++++++++++++++++ examples/libraries/uiwidgets.js | 772 +++++++++++++++++++++++ 2 files changed, 1784 insertions(+) create mode 100644 examples/games/solarsystem.js create mode 100644 examples/libraries/uiwidgets.js diff --git a/examples/games/solarsystem.js b/examples/games/solarsystem.js new file mode 100644 index 0000000000..94e76ece08 --- /dev/null +++ b/examples/games/solarsystem.js @@ -0,0 +1,1012 @@ +// +// solarsystem.js +// games +// +// Created by Bridget Went, 5/28/15. +// Copyright 2015 High Fidelity, Inc. +// +// The start to a project to build a virtual physics classroom to simulate the solar system, gravity, and orbital physics. +// - A sun with oribiting planets is created in front of the user +// - UI elements allow for adjusting the period, gravity, trails, and energy recalculations +// - Click "PAUSE" to pause the animation and show planet labels +// - In this mode, double-click a planet label to zoom in on that planet +// -Double-clicking on earth label initiates satellite orbiter game +// -Press "TAB" to toggle back to solar system view +// +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +// Script.include([ +// 'games/satellite.js' +// ]); + +var DAMPING = 0.0; +var LIFETIME = 6000; +var BASE_URL = "https://s3.amazonaws.com/hifi-public/marketplace/hificontent/Scripts/planets/planets/"; + +// Save intiial avatar and camera position +var startingPosition = { + x: 8000, + y: 8000, + z: 8000 +}; +MyAvatar.position = startingPosition; +var cameraStart = Camera.getOrientation(); + + +// Place the sun +var MAX_RANGE = 80.0; +var center = Vec3.sum(startingPosition, Vec3.multiply(MAX_RANGE, Vec3.normalize(Quat.getFront(Camera.getOrientation())))); +var SUN_SIZE = 7.0; + +var theSun = Entities.addEntity({ + type: "Model", + modelURL: BASE_URL + "sun.fbx", + position: center, + dimensions: { + x: SUN_SIZE, + y: SUN_SIZE, + z: SUN_SIZE + }, + angularVelocity: { + x: 0.0, + y: 0.1, + z: 0.0 + }, + angularDamping: DAMPING, + damping: DAMPING, + ignoreCollisions: false, + lifetime: LIFETIME, + collisionsWillMove: false +}); + + + +// Reference values for physical constants +var referenceRadius = 15.0; +var referenceDiameter = 0.6; +var referencePeriod = 3.0; +var LARGE_BODY_MASS = 250.0; +var SMALL_BODY_MASS = LARGE_BODY_MASS * 0.000000333; +var GRAVITY = (Math.pow(referenceRadius, 3.0) / Math.pow((referencePeriod / (2.0 * Math.PI)), 2.0)) / LARGE_BODY_MASS; +var REFERENCE_GRAVITY = GRAVITY; + +var planets = []; +var planetCount = 0; + +var TRAILS_ENABLED = false; +var MAX_POINTS_PER_LINE = 10; +var LINE_DIM = 200; +var LINE_WIDTH = 20; + +var VELOCITY_OFFSET_Y = -0.3; +var VELOCITY_OFFSET_Z = 0.9; + +var index = 0; + +var Planet = function(name, trailColor, radiusScale, sizeScale) { + + this.name = name; + this.trailColor = trailColor; + + this.trail = []; + this.lineStack = []; + + this.radius = radiusScale * referenceRadius; + this.position = Vec3.sum(center, { + x: this.radius, + y: 0.0, + z: 0.0 + }); + this.period = (2.0 * Math.PI) * Math.sqrt(Math.pow(this.radius, 3.0) / (GRAVITY * LARGE_BODY_MASS)); + this.gravity = GRAVITY; + this.initialVelocity = Math.sqrt((GRAVITY * LARGE_BODY_MASS) / this.radius); + this.velocity = Vec3.multiply(this.initialVelocity, Vec3.normalize({ + x: 0, + y: VELOCITY_OFFSET_Y, + z: VELOCITY_OFFSET_Z + })); + this.dimensions = sizeScale * referenceDiameter; + + this.planet = Entities.addEntity({ + type: "Model", + modelURL: BASE_URL + name + ".fbx", + position: this.position, + dimensions: { + x: this.dimensions, + y: this.dimensions, + z: this.dimensions + }, + velocity: this.velocity, + angularDamping: DAMPING, + damping: DAMPING, + ignoreCollisions: false, + lifetime: LIFETIME, + collisionsWillMove: true, + }); + + this.computeAcceleration = function() { + var acc = -(this.gravity * LARGE_BODY_MASS) * Math.pow(this.radius, (-2.0)); + return acc; + }; + + this.update = function(timeStep) { + var between = Vec3.subtract(this.position, center); + var speed = this.computeAcceleration(this.radius) * timeStep; + var vel = Vec3.multiply(speed, Vec3.normalize(between)); + + // Update velocity and position + this.velocity = Vec3.sum(this.velocity, vel); + this.position = Vec3.sum(this.position, Vec3.multiply(timeStep, this.velocity)); + Entities.editEntity(this.planet, { + velocity: this.velocity, + position: this.position + }); + + if (TRAILS_ENABLED) { + this.updateTrail(); + } + }; + + this.resetTrails = function() { + elapsed = 0.0; + + this.trail = []; + this.lineStack = []; + //add the first line to both the line entity stack and the trail + this.trail.push(newLine(this.lineStack, this.position, this.period, this.trailColor)); + + }; + + this.updateTrail = function() { + var point = this.position; + var linePos = Entities.getEntityProperties(this.lineStack[this.lineStack.length - 1]).position; + + this.trail.push(computeLocalPoint(linePos, point)); + + Entities.editEntity(this.lineStack[this.lineStack.length - 1], { + linePoints: this.trail + }); + + if (this.trail.length == MAX_POINTS_PER_LINE) { + this.trail = newLine(this.lineStack, point, this.period, this.trailColor); + } + }; + + this.zoom = function() { + var offset = { + x: 0.0, + y: 0.0, + z: 1.2 * sizeScale, + }; + MyAvatar.position = Vec3.sum(offset, this.position); + }; + + this.adjustPeriod = function(alpha) { + // Update global G constant, period, poke velocity to new value + var ratio = this.last_alpha / alpha; + this.gravity = Math.pow(ratio, 2.0) * GRAVITY; + this.period = ratio * this.period; + this.velocity = Vec3.multiply(ratio, this.velocity); + + this.last_alpha = alpha; + }; + + + index++; + this.resetTrails(); + +} + + +var MERCURY_LINE_COLOR = { + red: 255, + green: 255, + blue: 255 +}; +var VENUS_LINE_COLOR = { + red: 255, + green: 160, + blue: 110 +}; +var EARTH_LINE_COLOR = { + red: 10, + green: 150, + blue: 160 +}; +var MARS_LINE_COLOR = { + red: 180, + green: 70, + blue: 10 +}; +var JUPITER_LINE_COLOR = { + red: 250, + green: 140, + blue: 0 +}; +var SATURN_LINE_COLOR = { + red: 235, + green: 215, + blue: 0 +}; +var URANUS_LINE_COLOR = { + red: 135, + green: 205, + blue: 240 +}; +var NEPTUNE_LINE_COLOR = { + red: 30, + green: 140, + blue: 255 +}; +var PLUTO_LINE_COLOR = { + red: 255, + green: 255, + blue: 255 +}; + +planets.push(new Planet("mercury", MERCURY_LINE_COLOR, 0.387, 0.383)); +planets.push(new Planet("venus", VENUS_LINE_COLOR, 0.723, 0.949)); +planets.push(new Planet("earth", EARTH_LINE_COLOR, 1.0, 1.0)); +planets.push(new Planet("mars", MARS_LINE_COLOR, 1.52, 0.532)); +planets.push(new Planet("jupiter", JUPITER_LINE_COLOR, 5.20, 11.21)); +planets.push(new Planet("saturn", SATURN_LINE_COLOR, 9.58, 9.45)); +planets.push(new Planet("uranus", URANUS_LINE_COLOR, 19.20, 4.01)); +planets.push(new Planet("neptune", NEPTUNE_LINE_COLOR, 30.05, 3.88)); +planets.push(new Planet("pluto", PLUTO_LINE_COLOR, 39.48, 0.186)); + +var LABEL_X = 8.0; +var LABEL_Y = 3.0; +var LABEL_Z = 1.0; +var LABEL_DIST = 8.0; +var TEXT_HEIGHT = 1.0; + +var PlanetLabel = function(name, index) { + var text = name + " Speed: " + Vec3.length(planets[index].velocity).toFixed(2); + this.labelPos = Vec3.sum(planets[index].position, { + x: 0.0, + y: LABEL_DIST, + z: LABEL_DIST + }); + this.linePos = planets[index].position; + + this.line = Entities.addEntity({ + type: "Line", + position: this.linePos, + dimensions: { + x: 20, + y: 20, + z: 20 + }, + lineWidth: LINE_WIDTH, + color: { + red: 255, + green: 255, + blue: 255 + }, + linePoints: [{ + x: 0, + y: 0, + z: 0 + }, + computeLocalPoint(this.linePos, this.labelPos) + ], + visible: false + }); + + this.label = Entities.addEntity({ + type: "Text", + text: text, + lineHeight: TEXT_HEIGHT, + dimensions: { + x: LABEL_X, + y: LABEL_Y, + z: LABEL_Z + }, + position: this.labelPos, + backgroundColor: { + red: 10, + green: 10, + blue: 10 + }, + faceCamera: true, + visible: false + }); + + this.show = function() { + this.showing = true; + Entities.editEntity(this.line, { + visible: true + }); + Entities.editEntity(this.label, { + visible: true + }); + } + + this.hide = function() { + this.showing = false; + Entities.editEntity(this.line, { + visible: false + }); + Entities.editEntity(this.label, { + visible: false + }); + } +} + + +var time = 0.0; +var elapsed; +var TIME_STEP = 60.0; +var dt = 1.0 / TIME_STEP; + +var planetLines = []; +var trails = []; +var paused = false; + +function update(deltaTime) { + if (paused) { + return; + } + //deltaTime = dt; + time++; + if (time % TIME_STEP == 0) { + elapsed++; + } + + for (var i = 0; i < planets.length; ++i) { + planets[i].update(deltaTime); + } +} + +function pause() { + for (var i = 0; i < planets.length; ++i) { + Entities.editEntity(planets[i].planet, { + velocity: { + x: 0.0, + y: 0.0, + z: 0.0 + } + }); + planets[i].label = new PlanetLabel(planets[i].name, i); + planets[i].label.show(); + } + paused = true; +} + +function resume() { + for (var i = 0; i < planets.length; ++i) { + planets[i].label.hide(); + } + paused = false; +} + +function computeLocalPoint(linePos, worldPoint) { + var localPoint = Vec3.subtract(worldPoint, linePos); + return localPoint; +} + +// Create a new line +function newLine(lineStack, point, period, color) { + if (elapsed < period) { + var line = Entities.addEntity({ + position: point, + type: "Line", + color: color, + dimensions: { + x: LINE_DIM, + y: LINE_DIM, + z: LINE_DIM + }, + lifetime: LIFETIME, + lineWidth: LINE_WIDTH + }); + lineStack.push(line); + } else { + // Begin overwriting first lines after one full revolution (one period) + var firstLine = lineStack.shift(); + Entities.editEntity(firstLine, { + position: point, + linePoints: [{ + x: 0.0, + y: 0.0, + z: 0.0 + }] + }); + lineStack.push(firstLine); + + } + var points = []; + points.push(computeLocalPoint(point, point)); + return points; +} + + +var paddingX = 8; +var paddingY = 8; +var buttonWidth = 30; +var buttonHeight = 30; + +var ICONS_URL = 'https://s3.amazonaws.com/hifi-public/marketplace/hificontent/Scripts/planets/images/'; + +// var UIPanel = function(x, y, width, height) { +// this.visible = false; +// this.buttons = []; +// this.x = x; +// this.y = y; +// this.offsetX = paddingX; +// this.offsetY = paddingY; + +// this.background = Overlays.addOverlay("text", { +// backgroundColor: { +// red: 240, +// green: 240, +// blue: 255 +// }, +// textColor: { +// red: 10, +// green: 10, +// blue: 20 +// }, +// x: this.x, +// y: this.y, +// width: width, +// height: height, +// alpha: 1.0, +// backgroundAlpha: 0.7, +// visible: false +// }); + +// this.addIcon = function(iconID) { +// var icon = Overlays.addOverlay("image", { +// color: { +// red: 10, +// green: 10, +// blue: 10 +// }, +// imageURL: ICONS_URL + iconID + '.svg', +// x: this.x + this.offsetX, +// y: this.y + this.offsetY, +// width: buttonWidth, +// height: buttonHeight, +// alpha: 1.0, +// visible: false +// }); + +// if (width > height) { +// this.offsetX += buttonWidth + paddingX; +// } else { +// this.offsetY += buttonHeight + paddingY; +// } + + +// this.buttons.push(icon); +// return icon; +// } + +// this.addText = function(text) { +// var icon = Overlays.addOverlay("text", { +// color: { +// red: 240, +// green: 240, +// blue: 255 +// }, +// text: text, +// x: this.x + this.offsetX, +// y: this.y + this.offsetY, +// width: buttonWidth + paddingX * 4.0, +// height: buttonHeight, +// visible: false +// }); + +// if (width > height) { +// this.offsetX += buttonWidth + paddingX * 5.0; +// } else { +// this.offsetY += buttonHeight + paddingY * 5.0; +// } + + + +// this.buttons.push(icon); +// return icon; +// } + + +// this.show = function() { +// Overlays.editOverlay(this.background, { +// visible: true +// }); +// for (var i in this.buttons) { +// Overlays.editOverlay(this.buttons[i], { +// visible: true +// }); +// } +// this.visible = true; +// } + +// this.hide = function() { +// Overlays.editOverlay(this.background, { +// visible: false +// }); +// for (var i in this.buttons) { +// Overlays.editOverlay(this.buttons[i], { +// visible: false +// }); +// } +// this.visible = false; +// } + +// this.remove = function() { +// Overlays.deleteOverlay(this.background); +// for (var i in this.buttons) { +// Overlays.deleteOverlay(this.buttons[i]); +// } +// }; +// +// } + +var panelX = 1250; +var panelY = 500; +var panelWidth = 50; +var panelHeight = 210; + +// var mainPanel = new UIPanel(panelX, panelY, panelWidth, panelHeight); +// var systemViewButton = mainPanel.addIcon('solarsystems'); +// var zoomButton = mainPanel.addIcon('magnifier'); +// var satelliteButton = mainPanel.addIcon('satellite'); +// var settingsButton = mainPanel.addIcon('settings'); +// var stopButton = mainPanel.addIcon('close'); +// +// mainPanel.show(); +// +// var systemViewPanel = new UIPanel(panelX - 120, panelY, 120, 40); +// var reverseButton = systemViewPanel.addIcon('reverse'); +// var pauseButton = systemViewPanel.addIcon('playpause'); +// var forwardButton = systemViewPanel.addIcon('forward'); +// +// var zoomPanel = new UIPanel(panelX - 60, panelY + buttonHeight + paddingY, 650, 50); +// for (var i = 0; i < planets.length; ++i) { + // zoomPanel.addText(planets[i].name); +// } +Script.include('../libraries/uiwidgets.js'); + +UI.setDefaultVisibility(true); +UI.setErrorHandler(function(err) { + teardown(); + // print(err); + // Script.stop(); +}); + +// Controller.mouseMoveEvent.connect(function panelMouseMoveEvent(event) { return settings.mouseMoveEvent(event); }); +// Controller.mousePressEvent.connect( function panelMousePressEvent(event) { return settings.mousePressEvent(event); }); +// Controller.mouseDoublePressEvent.connect( function panelMouseDoublePressEvent(event) { return settings.mouseDoublePressEvent(event); }); +// Controller.mouseReleaseEvent.connect(function(event) { return settings.mouseReleaseEvent(event); }); +// Controller.keyPressEvent.connect(function(event) { return settings.keyPressEvent(event); }); + +// var ICON_WIDTH = 50.0; +// var ICON_HEIGHT = 50.0; +var ICON_WIDTH = 40.0; +var ICON_HEIGHT = 40.0; +var ICON_COLOR = UI.rgba(45, 45, 45, 0.7); +var FOCUSED_COLOR = UI.rgba(250, 250, 250, 1.0); + +var SUBPANEL_GAP = 1.0; + +function addIcon(panel, iconId) { + return panel.add(new UI.Icon({ + 'imageURL': ICONS_URL + iconId + '.svg', + 'width': ICON_WIDTH, + 'height': ICON_HEIGHT, + 'color': ICON_COLOR, + 'alpha': ICON_COLOR.a + })); +} + +var panel = { + background: { + backgroundColor: UI.rgb(50, 50, 50), + backgroundAlpha: 0.7, + textColor: UI.rgb(10, 10, 20) + }, + padding: { x: 7, y: 7 }, + border: { x: 12, y: 12 } +} + +// var panelContainer = new UI.WidgetContainer(); +// panelContainer.setPosition(500, 250); +// panelContainer.setVisible(true); + +var mainPanel = new UI.WidgetStack({ + dir: '+y', + padding: panel.padding, + border: panel.border, + background: panel.background +}); +// panelContainer.add(mainPanel); +mainPanel.setPosition(500, 250); +mainPanel.setVisible(true); + +var systemViewButton = addIcon(mainPanel, 'solarsystems'); +var zoomButton = addIcon(mainPanel, 'magnifier'); +var satelliteButton = addIcon(mainPanel, 'satellite'); +var settingsButton = addIcon(mainPanel, 'settings'); +var stopButton = addIcon(mainPanel, 'close'); +mainPanel.relayout(); + +var addColorToggle = function (widget) { + widget.addAction('onMouseOver', function () { + widget.setColor(FOCUSED_COLOR); + }); + widget.addAction('onMouseExit', function () { + widget.setColor(ICON_COLOR); + }); +} + +var systemViewPanel = new UI.WidgetStack({ + dir: '+x', + visible: true, + padding: panel.padding, + border: panel.border, + background: panel.background +}); + +UI.addAttachment({ + target: systemViewPanel, + rel: systemViewButton, + layout: function (target, rel) { + target.setPosition({ + x: rel.position - + }) + } +}); + +mainPanel.add({}); +// panelContainer.add(systemViewPanel); + +var reverseButton = addIcon(systemViewPanel, 'reverse'); +var pauseButton = addIcon(systemViewPanel, 'playpause'); +var forwardButton = addIcon(systemViewPanel, 'forward'); + + +var zoomPanel = new UI.WidgetStack({ + dir: '+y', + visible: false, + padding: panel.padding, + border: panel.border, + background: panel.background +}); +// panelContainer.add(zoomPanel); + + +UI.updateLayout(); + +function hideSubpanels () { + systemViewPanel.setVisible(false); + zoomPanel.setVisible(false); +} + +function attachTogglePanel (button, panel) { + button.addAction('onClick', function () { + var visible = !panel.visible; + + hideSubpanels(); + panel.setVisible(true); + panel.relayout(); + var offset = { + 'x': -(panel.getWidth() + panel.border.x + SUBPANEL_GAP), + 'y': -panel.border.y + }; + + panel.setPosition(Vec2.sum(button.position, offset)); + panel.setVisible(false); // force dirty + panel.setVisible(visible); + UI.updateLayout(); + }) +} + +// Panel drag behavior +// (click + drag on border to drag) +(function () { + var dragged = null; + this.startDrag = function (dragAction) { + dragged = dragAction; + } + this.updateDrag = function (event) { + if (dragged) { + print("Update drag"); + dragged.updateDrag(event); + } + } + this.clearDrag = function (event) { + if (dragged) + print("End drag"); + dragged = null; + } +})(); + +function makeDraggable (panel, target) { + if (!target) + target = panel; + + panel.addAction('onMouseDown', function (event) { + var dragStart = { x: event.x, y: event.y }; + var initialPos = { x: target.position.x, y: target.position.y }; + startDrag({ + updateDrag: function (event) { + target.setPosition({ + x: initialPos.x + event.x - dragStart.x, + y: initialPos.y + event.y - dragStart.y + }); + UI.updateLayout(); + } + }); + }); +} + +var buttons = [ systemViewButton, zoomButton, satelliteButton, settingsButton, stopButton, + reverseButton, pauseButton, forwardButton ]; +var panels = [ mainPanel, systemViewPanel, zoomPanel ]; + +buttons.map(addColorToggle); +panels.map(function (panel) { makeDraggable(panel, panelContainer); }); + + +attachTogglePanel(systemViewButton, systemViewPanel); +// systemViewButton.addAction('onMouseDown', function () { + // systemViewPanel.setVisible(true); + // UI.updateLayout(); +// }); + +reverseButton.addAction('onClick', function() { + +}); +pauseButton.addAction('onClick', function() { + // paused ? resume() : pause(); +}); + +zoomButton.addAction('onClick', function() { + hideSubpanels(); + UI.updateLayout(); + + if (zoomButton.visible) { + MyAvatar.position = startingPosition; + Camera.setOrientation(cameraStart); + // resume(); + } else { + // pause(); + } +}); +attachTogglePanel(zoomButton, zoomPanel); +UI.updateLayout(); + + +stopButton.addAction('onClick', function() { + // Script.stop(); + teardown(); +}); + + +// Script.include('../utilities/tools/cookies.js'); +// var settings; + +// var satelliteView; +// var satelliteGame; + +// // satelliteButton.addAction('onclick', function() { +// // if (satelliteView) { +// // satelliteGame.endGame(); +// // MyAvatar.position = startingPosition; +// // satelliteView = false; +// // resume(); +// // } else { +// // pause(); +// // var confirmed = Window.confirm("Start satellite game?"); +// // if (!confirmed) { +// // resume(); +// // return; +// // } +// // satelliteView = true; +// // MyAvatar.position = { +// // x: 200, +// // y: 200, +// // z: 200 +// // }; +// // Camera.setPosition({ +// // x: 200, +// // y: 200, +// // z: 200 +// // }); +// // satelliteGame = new SatelliteGame(); +// // } +// // }); + +// settingsButton.addAction('onclick', function() { +// if (!settings) { +// settings = new Panel(panelX - 610, panelY - 130); +// settings.visible = false; +// var g_multiplier = 1.0; +// settings.newSlider("Gravitational Force ", 0.1, 5.0, +// function(value) { +// g_multiplier = value; +// GRAVITY = REFERENCE_GRAVITY * g_multiplier; +// }, + +// function() { +// return g_multiplier; +// }, +// function(value) { +// return value.toFixed(1) + "x"; +// }); + +// var subPanel = settings.newSubPanel("Orbital Periods"); +// for (var i = 0; i < planets.length; ++i) { +// var period_multiplier = 1.0; +// var last_alpha = period_multiplier; + +// subPanel.newSlider(planets[i].name, 0.1, 3.0, +// function(value) { +// period_multiplier = value; +// planets[i].adjustPeriod(value); +// }, +// function() { +// return period_multiplier; +// }, +// function(value) { +// return (value).toFixed(2) + "x"; +// }); +// } +// settings.newCheckbox("Leave Trails: ", +// function(value) { +// TRAILS_ENABLED = value; +// if (TRAILS_ENABLED) { +// for (var i = 0; i < planets.length; ++i) { +// planets[i].resetTrails(); +// } +// //if trails are off and we've already created trails, remove existing trails +// } else { +// for (var i = 0; i < planets.length; ++i) { +// for (var j = 0; j < planets[i].lineStack.length; ++j) { +// Entities.editEntity(planets[i].lineStack[j], { +// visible: false +// }); +// } +// planets[i].lineStack = []; +// } +// } +// }, +// function() { +// return TRAILS_ENABLED; +// }, +// function(value) { +// return value; +// }); +// } else { +// settings.destroy(); +// settings = null; +// } +// }); + + + + + + + + +// function mousePressEvent(event) { +// var clickedOverlay = Overlays.getOverlayAtPoint({ +// x: event.x, +// y: event.y +// }); + +// if (clickedOverlay == systemViewButton) { +// if (systemViewPanel.visible) { +// systemViewPanel.hide(); +// } else { +// systemViewPanel.show(); +// } +// } +// if (clickedOverlay == pauseButton) { +// if (!paused) { +// pause(); +// } else { +// resume(); +// } + +// } +// if (clickedOverlay == zoomButton) { +// if (zoomPanel.visible) { +// zoomPanel.hide(); +// MyAvatar.position = startingPosition; +// Camera.setOrientation(cameraStart); +// resume(); +// } else { +// zoomPanel.show(); +// pause(); +// } +// } +// var zoomed = false; +// if (zoomPanel.visible) { +// for (var i = 0; i < planets.length; ++i) { +// if (clickedOverlay == zoomPanel.buttons[i]) { +// pause(); +// planets[i].zoom(); +// zoomed = true; +// } +// } +// } + + +// if (clickedOverlay == satelliteButton) { + + +// } + +// if (clickedOverlay == settingsButton) { + +// } + +// if(clickedOverlay == stopButton) { +// Script.stop(); +// } +// } + +UI.printWidgets(); + +// Clean up models, UI panels, lines, and button overlays +function teardown() { + UI.teardown(); + // mainPanel.remove(); + // systemViewPanel.remove(); + // zoomPanel.remove(); + // if (settings) { + // settings.destroy(); + // } + + Entities.deleteEntity(theSun); + for (var i = 0; i < planets.length; ++i) { + Entities.deleteEntity(planets[i].planet); + } + + var e = Entities.findEntities(MyAvatar.position, 16000); + for (i = 0; i < e.length; i++) { + var props = Entities.getEntityProperties(e[i]); + if (props.type === "Line" || props.type === "Text") { + Entities.deleteEntity(e[i]); + } + } +}; + +// Controller.mousePressEvent.connect(mousePressEvent); + +// if (settings) { +// Controller.mouseMoveEvent.connect(function panelMouseMoveEvent(event) { return settings.mouseMoveEvent(event); }); +// Controller.mousePressEvent.connect( function panelMousePressEvent(event) { return settings.mousePressEvent(event); }); +// Controller.mouseDoublePressEvent.connect( function panelMouseDoublePressEvent(event) { return settings.mouseDoublePressEvent(event); }); +// Controller.mouseReleaseEvent.connect(function(event) { return settings.mouseReleaseEvent(event); }); +// Controller.keyPressEvent.connect(function(event) { return settings.keyPressEvent(event); }); +// } + +var inputHandler = { + onMouseMove: function (event) { + updateDrag(event); + UI.handleMouseMove(event); + }, + onMousePress: function (event) { + UI.handleMousePress(event); + }, + onMouseRelease: function (event) { + clearDrag(event); + UI.handleMouseRelease(event); + } +}; +Controller.mousePressEvent.connect(inputHandler.onMousePress); +Controller.mouseMoveEvent.connect(inputHandler.onMouseMove); +Controller.mouseReleaseEvent.connect(inputHandler.onMouseRelease); + + + +Script.scriptEnding.connect(teardown); +Script.update.connect(update); \ No newline at end of file diff --git a/examples/libraries/uiwidgets.js b/examples/libraries/uiwidgets.js new file mode 100644 index 0000000000..129675b02c --- /dev/null +++ b/examples/libraries/uiwidgets.js @@ -0,0 +1,772 @@ +// +// uiwidgets.js +// examples/libraries +// +// Created by Seiji Emery, 8/10/15 +// Copyright 2015 High Fidelity, Inc +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + + +(function(){ + +// Setup externals +(function() { + +// We need a Vec2 impl, with add() and a clone function. If this is not part of hifi, we'll just add it: +if (this.Vec2 == undefined) { + var Vec2 = this.Vec2 = function (x, y) { + this.x = x || 0.0; + this.y = y || 0.0; + } + Vec2.sum = function (a, b) { + return new Vec2(a.x + b.x, a.y + b.y); + } + Vec2.clone = function (v) { + return new Vec2(v.x, v.y); + } +} else if (this.Vec2.clone == undefined) { + print("Vec2 exists; adding Vec2.clone"); + this.Vec2.clone = function (v) { + return { 'x': v.x || 0.0, 'y': v.y || 0.0 }; + } +} else { + print("Vec2...?"); +} +})(); + +var Rect = function (xmin, ymin, xmax, ymax) { + this.x0 = xmin; + this.y0 = ymin; + this.x1 = xmax; + this.y1 = ymax; +} +Rect.prototype.grow = function (pt) { + this.x0 = Math.min(this.x0, pt.x); + this.y0 = Math.min(this.y0, pt.y); + this.x1 = Math.max(this.x1, pt.x); + this.y1 = Math.max(this.y1, pt.y); +} +Rect.prototype.getWidth = function () { + return this.x1 - this.x0; +} +Rect.prototype.getHeight = function () { + return this.y1 - this.y0; +} +Rect.prototype.getTopLeft = function () { + return { 'x': this.x0, 'y': this.y0 }; +} +Rect.prototype.getBtmRight = function () { + return { 'x': this.x1, 'y': this.y1 }; +} +Rect.prototype.getCenter = function () { + return { + 'x': 0.5 * (this.x1 + this.x0), + 'y': 0.5 * (this.y1 + this.y0) + }; +} + +var __trace = new Array(); +var __traceDepth = 0; + +var assert = function (cond, expr) { + if (!cond) { + var callstack = ""; + var maxRecursion = 10; + caller = arguments.callee.caller; + while (maxRecursion > 0 && caller) { + --maxRecursion; + callstack += ">> " + caller.toString(); + caller = caller.caller; + } + throw new Error("assertion failed: " + expr + " (" + cond + ")" + "\n" + + "Called from: " + callstack + " " + + "Traceback: \n\t" + __trace.join("\n\t")); + } +} +var traceEnter = function(fcn) { + var l = __trace.length; + // print("TRACE ENTER: " + (l+1)); + s = ""; + for (var i = 0; i < __traceDepth+1; ++i) + s += "-"; + ++__traceDepth; + __trace.push(s + fcn); + __trace.push(__trace.pop() + ":" + this); + return { + 'exit': function () { + --__traceDepth; + // while (__trace.length != l) + // __trace.pop(); + } + }; +} + +// UI namespace +var UI = this.UI = {}; + +var rgb = UI.rgb = function (r, g, b) { + if (typeof(r) == 'string') { + rs = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(r); + if (rs) { + r = parseInt(rs[0], 16); + g = parseInt(rs[1], 16); + b = parseInt(rs[2], 16); + } + } + if (typeof(r) != 'number' || typeof(g) != 'number' || typeof(b) != 'number') { + ui.err("Invalid args to UI.rgb (" + r + ", " + g + ", " + b + ")"); + return null; + } + return { 'r': r, 'g': g, 'b': b }; +} +var rgba = UI.rgba = function (r, g, b, a) { + if (typeof(r) == 'string') + return rgb(r); + return { 'r': r || 0, 'g': g || 0, 'b': b || 0, 'a': a }; +} + +// Protected UI state +var ui = { + defaultVisible: true, + widgetList: new Array(), + attachmentList: new Array() +}; + +ui.complain = function (msg) { + print("WARNING (uiwidgets.js): " + msg); +} +ui.errorHandler = function (err) { + print(err); +} +ui.assert = function (condition, message) { + if (!condition) { + message = "FAILED ASSERT (uiwidgets.js): " + message || "(" + condition + ")"; + ui.errorHandler(message); + if (typeof(Error) !== 'undefined') + throw new Error(message); + throw message; + } +} + +UI.setDefaultVisibility = function (visible) { + ui.defaultVisible = visible; +} + +// Hide overlays impl +function makeOverlay(type, properties) { + var _TRACE = traceEnter.call(this, "makeOverlay"); + var overlay = Overlays.addOverlay(type, properties); + // overlay.update = function (properties) { + // Overlays.editOverlay(overlay, properties); + // } + // overlay.destroy = function () { + // Overlays.deleteOverlay(overlay); + // } + // return overlay; + _TRACE.exit(); + return { + 'update': function (properties) { + var _TRACE = traceEnter.call(this, "Overlay.update"); + Overlays.editOverlay(overlay, properties); + _TRACE.exit(); + }, + 'destroy': function () { + var _TRACE = traceEnter.call(this, "Overlay.destroy"); + Overlays.deleteOverlay(overlay); + _TRACE.exit(); + }, + 'getId': function () { + return overlay; + } + } +} + +var COLOR_WHITE = rgb(255, 255, 255); +var COLOR_GRAY = rgb(125, 125, 125); + +// Widget base class +var Widget = function () {}; + +__widgetId = 0; + +Widget.prototype.constructor = function () { + var _TRACE = traceEnter.call(this, "Widget.constructor()"); + this.position = { 'x': 0.0, 'y': 0.0 }; + this.visible = ui.defaultVisible; + this.actions = {}; + this._dirty = true; + this.parent = null; + this._parentVisible = true; + this.id = __widgetId++; + ui.widgetList.push(this); + assert(this.position, "this.position != undefined"); + _TRACE.exit(); +} + +Widget.prototype.setPosition = function () { + var _TRACE = traceEnter.call(this, "Widget.setPosition()"); + if (arguments.length == 1) { + (function (pos) { + this.position = { x: pos.x, y: pos.y }; + }).apply(this, arguments); + } else { + (function (x, y) { + this.position = { x: x, y: y }; + }).apply(this, arguments); + } + this.setDirty(); + _TRACE.exit(); +} +Widget.prototype.setVisible = function(visible) { + var _TRACE = traceEnter.call(this, "Widget.setVisible()"); + if (this.visible != visible) { + this.setDirty(); + } + this.visible = visible; + _TRACE.exit(); +} +Widget.prototype.isVisible = function () { + return this.visible && this._parentVisible; +} +Widget.prototype.getOverlay = function () { + return null; +} +Widget.prototype.addAction = function (name, callback) { + var _TRACE = traceEnter.call(this, "Widget.addAction()"); + if (!this.actions[name]) { + this.actions[name] = [ callback ]; + } else { + this.actions[name].push(callback); + } + _TRACE.exit(); +} +Widget.prototype.setDirty = function () { + this._dirty = true; +} + +var __first = 0; +Widget.prototype.toString = function () { + // var _TRACE = (__trace[__trace.length - 1] != "Widget.toString()") && __first++ == 0 ? + // traceEnter.call(this, "Widget.toString()") : { 'exit': function(){} }; + // assert(this.position, "this.position != undefined"); + // _TRACE.exit(); + return "[Widget " + this.id + " ]"//+" (pos=(" + this.position.x + ", " + this.position.y + "), parent=" + this.parent + ")"; +} + +var WidgetContainer = UI.WidgetContainer = function () { + Widget.prototype.constructor.call(this); + this.children = new Array(); + this._lastPos = { x: this.position.x, y: this.position.y }; +}; +WidgetContainer.prototype = new Widget(); +WidgetContainer.prototype.constructor = WidgetContainer; + +// WidgetContainer.prototype.setPosition = function (pos) { +// if (arguments.length > 1) +// pos = { x: arguments[0], y: arguments[1] }; +// this.newPos = pos; +// this.setDirty(); +// } + +WidgetContainer.prototype.add = function (child) { + this.children.push(child); + child.parent = this; + return child; +} +WidgetContainer.prototype.relayout = function (layout) { + var rx = this.position.x - this._lastPos.x; + var ry = this.position.y - this._lastPos.y; + if (rx || ry) { + this.children.forEach(function (child) { + child.setPosition(child.position.x + rx, child.position.y + ry); + child.relayout(layout); + }, this); + } + this._lastPos.x = this.position.x; + this._lastPos.y = this.position.y; + this._dirty = false; +} +WidgetContainer.prototype.getWidth = function () { + return 0; +} +WidgetContainer.prototype.getHeight = function () { + return 0; +} +WidgetContainer.prototype.hasOverlay = function () { + return false; +} + +// Inherits from Widget +var WidgetStack = UI.WidgetStack = function (properties) { + var _TRACE = traceEnter.call(this, "WidgetStack.constructor()"); + Widget.prototype.constructor.call(this); + assert(ui.widgetList[ui.widgetList.length-1] === this, "ui.widgetList.back() == this"); + + properties = properties || {}; + properties['dir'] = properties['dir'] || '+y'; + + var dir = undefined; + switch(properties['dir']) { + case '+y': dir = { 'x': 0.0, 'y': 1.0 }; break; + case '-y': dir = { 'x': 0.0, 'y': -1.0 }; break; + case '+x': dir = { 'x': 1.0, 'y': 0.0 }; break; + case '-x': dir = { 'x': -1.0, 'y': 0.0 }; break; + default: ui.complain("Unrecognized UI.WidgetStack property 'dir': \"" + dir + "\""); + } + dir = dir || { 'x': 1.0, 'y': 0.0 }; + + this.layoutDir = dir; + this.border = properties.border || { 'x': 0.0, 'y': 0.0 }; + this.padding = properties.padding || { 'x': 0.0, 'y': 0.0 }; + this.visible = properties.visible != undefined ? properties.visible : this.visible; + + if (properties.background) { + var background = properties.background; + background.x = this.position ? this.position.x : 0; + background.y = this.position ? this.position.y : 0; + background.width = background.width || 100.0; + background.height = background.height || 100.0; + background.backgroundColor = background.backgroundColor || COLOR_GRAY; + background.backgroundAlpha = background.backgroundAlpha || 0.5; + background.textColor = background.textColor || COLOR_WHITE; + background.alpha = background.alpha || 1.0; + background.visible = this.visible; + this.backgroundOverlay = makeOverlay("text", background); + } + + this.widgets = new Array(); + + _TRACE.exit(); +} +WidgetStack.prototype = new Widget(); +WidgetStack.prototype.constructor = WidgetStack; + +WidgetStack.prototype.toString = function () { + return "[WidgetStack " + this.id + " ]"; +} + +WidgetStack.prototype.add = function (widget) { + var _TRACE = traceEnter.call(this, "WidgetStack.add()"); + this.widgets.push(widget); + widget.parent = this; + this.setDirty(); + + assert(this.widgets, "this.widgets != undefined"); + + _TRACE.exit(); + return widget; +} +WidgetStack.prototype.relayout = function (layoutFrom) { + var _TRACE = traceEnter.call(this, "WidgetStack.relayout()"); + // print("this = " + this); + // print("this.position = { " + this.position.x + ", " + this.position.y + " }") + + // this.position.x += 500; + + // this.setPosition({ + // x: this.position.x + 5, + // y: this.position.y + // }); + + var layoutStart = { + 'x': this.position.x + this.border.x, + 'y': this.position.y + this.border.y + } + + var layout = { + 'pos': Vec2.clone(layoutStart), + 'bounds': new Rect(this.position.x, this.position.y, this.position.x, this.position.y) + }; + // print("this.widgets = " + this.widgets); + assert(this.widgets, "this.widgets != undefined"); + + if (this.parent == null) + this._parentVisible = true; + var this_isVisible = this.isVisible(); + + this.widgets.forEach(function (child, i, elems) { + child.setPosition(layout.pos.x, layout.pos.y); + child._parentVisible = this_isVisible; + child.relayout(layout); + + if (child.isVisible()) { + layout.bounds.grow({ + 'x': layout.pos.x + child.getWidth(), + 'y': layout.pos.y + child.getHeight() + }); + layout.pos.x += (child.getWidth() + this.padding.x) * this.layoutDir.x; + layout.pos.y += (child.getHeight() + this.padding.y) * this.layoutDir.y; + } + // elems[i] = child; + }, this); + + this._dirty = false; + + layout.bounds.grow(Vec2.sum(layout.bounds.getBtmRight(), this.border)); + layout.pos.x += this.border.x * this.layoutDir.x; + layout.pos.y += this.border.y * this.layoutDir.y; + + this._calculatedDimensions = { + 'x': layout.bounds.getWidth(), + 'y': layout.bounds.getHeight() + }; + if (this.backgroundOverlay) { + this.backgroundOverlay.update({ + 'x': this.position.x,// - this.border.x, + 'y': this.position.y,// - this.border.y, + 'width': this._calculatedDimensions.x, + 'height': this._calculatedDimensions.y, + 'visible': this.visible + }); + } + print("RELAYOUT " + this); + print("layoutDir: " + this.layoutDir.x + ", " + this.layoutDir.y); + print("padding: " + this.padding.x + ", " + this.padding.y); + print("topleft: " + layout.bounds.getTopLeft().x + ", " + layout.bounds.getTopLeft().y); + print("btmright: " + layout.bounds.getBtmRight().x + ", " + layout.bounds.getBtmRight().y); + print("width, height = " + layout.bounds.getWidth() + ", " + layout.bounds.getHeight()); + print("start: " + this.position.x + ", " + this.position.y); + print("end: " + layout.pos.x + ", " + layout.pos.y); + + + if (layoutFrom) { + layoutFrom.pos = layout.pos; + layoutFrom.bounds.grow(layout.getTopLeft()); + layoutFrom.bounds.grow(layout.getBtmRight()); + } + _TRACE.exit(); +} +WidgetStack.prototype.getWidth = function () { + var _TRACE = traceEnter.call(this, "WidgetStack.getWidth()"); + if (this._dirty || !this._calculatedDimensions) + this.relayout(); + _TRACE.exit(); + return this._calculatedDimensions.x; +} +WidgetStack.prototype.getHeight = function () { + var _TRACE = traceEnter.call(this, "WidgetStack.getHeight()"); + if (this._dirty || !this._calculatedDimensions) + this.relayout(); + _TRACE.exit(); + return this._calculatedDimensions.y; +} +WidgetStack.prototype.show = function () { + this.relayout(); +} +WidgetStack.prototype.hasOverlay = function (overlayId) { + return this.backgroundOverlay && this.backgroundOverlay.getId() === overlayId; +} +WidgetStack.prototype.getOverlay = function () { + return this.backgroundOverlay; +} +WidgetStack.prototype.setDirty = function () { + this._dirty = true; + this.widgets.forEach(function(widget){ + widget.setDirty(); + }); +} +WidgetStack.prototype.destroy = function () { + var _TRACE = traceEnter.call(this, "WidgetStack.destroy()"); + if (this.backgroundOverlay) { + this.backgroundOverlay.destroy(); + this.backgroundOverlay = null; + } + _TRACE.exit(); +} +WidgetStack.prototype.setColor = function (color) { + if (arguments.length != 1) { + color = rgba.apply(arguments); + } + this.backgroundOverlay.update({ + 'color': color, + 'alpha': color.a + }); +} + +var Icon = UI.Icon = function (properties) { + var _TRACE = traceEnter.call(this, "Icon.constructor()"); + Widget.prototype.constructor.call(this); + + this.visible = properties.visible != undefined ? properties.visible : this.visible; + this.width = properties.width || 1.0; + this.height = properties.height || 1.0; + + var iconProperties = { + 'color': properties.color || COLOR_GRAY, + 'alpha': properties.alpha || 1.0, + 'imageURL': properties.imageURL, + 'width': this.width, + 'height': this.height, + 'x': this.position ? this.position.x : 0.0, + 'y': this.position ? this.position.y : 0.0, + 'visible': this.visible + } + this.iconOverlay = makeOverlay("image", iconProperties); + _TRACE.exit() +} +Icon.prototype = new Widget(); +Icon.prototype.constructor = Icon; +Icon.prototype.toString = function () { + return "[UI.Icon " + this.id + " ]"; +} + +Icon.prototype.relayout = function (layout) { + var _TRACE = traceEnter.call(this, "Icon.relayout()"); + this._dirty = false; + this.iconOverlay.update({ + 'x': this.position.x, + 'y': this.position.y, + 'width': this.width, + 'height': this.height, + 'visible': this.visible + }); + _TRACE.exit() +} +Icon.prototype.getHeight = function () { + return this.height; +} +Icon.prototype.getWidth = function () { + return this.width; +} +Icon.prototype.hasOverlay = function (overlayId) { + return this.iconOverlay.getId() === overlayId; +} +Icon.prototype.getOverlay = function () { + return this.iconOverlay; +} + +Icon.prototype.destroy = function () { + if (this.iconOverlay) { + this.iconOverlay.destroy(); + this.iconOverlay = null; + } +} +Icon.prototype.setColor = function (color) { + if (arguments.length != 1) { + color = rgba.apply(arguments); + } + this.iconOverlay.update({ + 'color': color, + 'alpha': color.a + }); +} + +var Attachment = UI.Attachment = function (target, impl) { + Widget.prototype.constructor.apply(this); + this.rel = { + x: target.x - this.position.x, + y: target.y - this.position.y + }; +} +Attachment.prototype.setDirty = function (layout) { + this._dirty = true; + this.target.setDirty(); +} +Attachment.prototype.relayout = function (layout) { + targetPos = Vec2.sum(this.position, this.target.position); +} + +// UI.updateLayout = function () { +// var touched = {}; + +// function recalcDimensions(widget) { +// if (widget.parent && !touched[widget.parent.id]) +// recalcDimensions(widget.parent); +// touched[widget.id] = true; +// widget.cachedWidth = widget.calcWidth(); +// widget.cachedHeight = widget.calcHeight(); +// } +// widgetList.forEach(function (widget) { +// if (!touched[widget.id]) { +// recalcDimensions(widget); +// } +// }); +// } + +UI.setDefaultVisibility = function(visibility) { + ui.defaultVisible = visibility; +}; +UI.updateLayout = function () { + var _TRACE = traceEnter.call(this, "UI.updateLayout()"); + ui.widgetList.forEach(function(widget, index, array) { + assert(typeof(widget) == 'object', 'typeof(widget) == "object"'); + if (widget._dirty) { + var top = widget; + while (top.parent && top.parent._dirty) + top = top.parent; + top.relayout(); + if (widget !== top && widget._dirty) + widget.relayout(); + assert(!widget._dirty, "widget._dirty == false"); + } + }); + _TRACE.exit(); +}; + +function dispatchEvent(actions, widget, event) { + var _TRACE = traceEnter.call(this, "UI.dispatchEvent()"); + actions.forEach(function(action) { + action.call(widget, event); + }); + _TRACE.exit(); +} + +ui.focusedWidget = null; +// ui.selectedWidget = null; + +var getWidgetWithOverlay = function (overlay) { + // print("trying to find overlay: " + overlay); + + var foundWidget = null; + ui.widgetList.forEach(function(widget) { + if (widget.hasOverlay(overlay)) { + // print("found overlay in " + widget); + foundWidget = widget; + return; + } + }); + // if (!foundWidget) + // print("could not find overlay"); + return foundWidget; +} + +var getFocusedWidget = function (event) { + return getWidgetWithOverlay(Overlays.getOverlayAtPoint({ 'x': event.x, 'y': event.y })); +} + +var dispatchEvent = function (action, event, widget) { + function dispatchActions (actions) { + actions.forEach(function(action) { + action(event, widget); + }); + } + + if (widget.actions[action]) { + print("Dispatching action '" + action + "'' to " + widget); + dispatchActions(widget.actions[action]); + } else { + for (var parent = widget.parent; parent != null; parent = parent.parent) { + if (parent.actions[action]) { + print("Dispatching action '" + action + "'' to parent widget " + widget); + dispatchActions(parent.actions[action]); + return; + } + } + print("No action '" + action + "' in " + widget); + } +} + +UI.handleMouseMove = function (event) { + // print("mouse moved x = " + event.x + ", y = " + event.y); + var focused = getFocusedWidget(event); + + print("got focus: " + focused); + + if (focused != ui.focusedWidget) { + if (focused) + dispatchEvent('onMouseOver', event, focused); + if (ui.focusedWidget) + dispatchEvent('onMouseExit', event, ui.focusedWidget); + ui.focusedWidget = focused; + } +} + +UI.handleMousePress = function (event) { + print("Mouse clicked"); + UI.handleMouseMove(event); + if (ui.focusedWidget) { + dispatchEvent('onMouseDown', event, ui.focusedWidget); + } +} + +UI.handleMouseRelease = function (event) { + print("Mouse released"); + UI.handleMouseMove(event); + if (ui.focusedWidget) { + dispatchEvent('onMouseUp', event, ui.focusedWidget); + dispatchEvent('onClick', event, ui.focusedWidget); + } +} + +UI.teardown = function () { + print("Teardown"); + ui.widgetList.forEach(function(widget) { + widget.destroy(); + }); + ui.widgetList = []; + ui.focusedWidget = null; +}; +UI.setErrorHandler = function (errorHandler) { + if (typeof(errorHandler) !== 'function') { + ui.complain("UI.setErrorHandler -- invalid argument: \"" + errorHandler + "\""); + } else { + ui.errorHandler = errorHandler; + } +} + +UI.printWidgets = function () { + print("widgetlist.length = " + ui.widgetList.length); + ui.widgetList.forEach(function(widget) { + print(""+widget + " position=(" + widget.position.x + ", " + widget.position.y + ")" + + " parent = " + widget.parent + " visible = " + widget.isVisible() + + " width = " + widget.getWidth() + ", height = " + widget.getHeight() + + " overlay = " + (widget.getOverlay() && widget.getOverlay().getId()) + + (widget.border ? " border = " + widget.border.x + ", " + widget.border.y : "") + + (widget.padding ? " padding = " + widget.padding.x + ", " + widget.padding.y : "")); + }); +} + +})(); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From a5d8125aa784c5e690565ec56a8ba415cbc6cabb Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Tue, 11 Aug 2015 17:53:49 -0700 Subject: [PATCH 38/53] Remove obsolete AvatarMixer wire data. (Was duplicated in joint states that are also sent.) --- libraries/avatars/src/AvatarData.cpp | 56 ++----------------- .../networking/src/udt/PacketHeaders.cpp | 2 + libraries/networking/src/udt/PacketHeaders.h | 1 + 3 files changed, 7 insertions(+), 52 deletions(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 5b970a95a3..48e613e2da 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -161,16 +161,6 @@ QByteArray AvatarData::toByteArray() { // Body scale destinationBuffer += packFloatRatioToTwoByte(destinationBuffer, _targetScale); - // Head rotation - destinationBuffer += packFloatAngleToTwoByte(destinationBuffer, _headData->getFinalPitch()); - destinationBuffer += packFloatAngleToTwoByte(destinationBuffer, _headData->getFinalYaw()); - destinationBuffer += packFloatAngleToTwoByte(destinationBuffer, _headData->getFinalRoll()); - - // Body lean - destinationBuffer += packFloatAngleToTwoByte(destinationBuffer, _headData->_leanForward); - destinationBuffer += packFloatAngleToTwoByte(destinationBuffer, _headData->_leanSideways); - destinationBuffer += packFloatAngleToTwoByte(destinationBuffer, _headData->_torsoTwist); - // Lookat Position memcpy(destinationBuffer, &_headData->_lookAtPosition, sizeof(_headData->_lookAtPosition)); destinationBuffer += sizeof(_headData->_lookAtPosition); @@ -278,25 +268,20 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { quint64 now = usecTimestampNow(); // The absolute minimum size of the update data is as follows: - // 50 bytes of "plain old data" { + // 36 bytes of "plain old data" { // position = 12 bytes // bodyYaw = 2 (compressed float) // bodyPitch = 2 (compressed float) // bodyRoll = 2 (compressed float) // targetScale = 2 (compressed float) - // headPitch = 2 (compressed float) - // headYaw = 2 (compressed float) - // headRoll = 2 (compressed float) - // leanForward = 2 (compressed float) - // leanSideways = 2 (compressed float) - // torsoTwist = 2 (compressed float) // lookAt = 12 // audioLoudness = 4 // } + // + 1 byte for varying data // + 1 byte for pupilSize // + 1 byte for numJoints (0) - // = 51 bytes - int minPossibleSize = 51; + // = 39 bytes + int minPossibleSize = 39; int maxAvailableSize = buffer.size(); if (minPossibleSize > maxAvailableSize) { @@ -354,39 +339,6 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { _targetScale = scale; } // 20 bytes - { // Head rotation - //(NOTE: This needs to become a quaternion to save two bytes) - float headYaw, headPitch, headRoll; - sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &headPitch); - sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &headYaw); - sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &headRoll); - if (glm::isnan(headYaw) || glm::isnan(headPitch) || glm::isnan(headRoll)) { - if (shouldLogError(now)) { - qCDebug(avatars) << "Discard nan AvatarData::headYaw,headPitch,headRoll; displayName = '" << _displayName << "'"; - } - return maxAvailableSize; - } - _headData->setBasePitch(headPitch); - _headData->setBaseYaw(headYaw); - _headData->setBaseRoll(headRoll); - } // 6 bytes - - { // Head lean (relative to pelvis) - float leanForward, leanSideways, torsoTwist; - sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*)sourceBuffer, &leanForward); - sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*)sourceBuffer, &leanSideways); - sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*)sourceBuffer, &torsoTwist); - if (glm::isnan(leanForward) || glm::isnan(leanSideways)) { - if (shouldLogError(now)) { - qCDebug(avatars) << "Discard nan AvatarData::leanForward,leanSideways,torsoTwise; displayName = '" << _displayName << "'"; - } - return maxAvailableSize; - } - _headData->_leanForward = leanForward; - _headData->_leanSideways = leanSideways; - _headData->_torsoTwist = torsoTwist; - } // 6 bytes - { // Lookat Position glm::vec3 lookAt; memcpy(&lookAt, sourceBuffer, sizeof(lookAt)); diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 0146abdb7c..db0b5394fa 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -68,6 +68,8 @@ PacketVersion versionForPacketType(PacketType::Value packetType) { case EntityEdit: case EntityData: return VERSION_ENTITIES_POLYLINE; + case AvatarData: + return VERSION_AVATAR_NO_LEAN; default: return 11; } diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 1aeadb1af9..3f408d1329 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -142,5 +142,6 @@ const PacketVersion VERSION_ENTITIES_HAVE_SIMULATION_OWNER_AND_ACTIONS_OVER_WIRE const PacketVersion VERSION_ENTITIES_NEW_PROTOCOL_LAYER = 35; const PacketVersion VERSION_POLYVOX_TEXTURES = 36; const PacketVersion VERSION_ENTITIES_POLYLINE = 37; +const PacketVersion VERSION_AVATAR_NO_LEAN = 38; #endif // hifi_PacketHeaders_h \ No newline at end of file From 0f56285283bc67e3f9521969dcebf18c1b4803c2 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 11 Aug 2015 18:54:55 -0700 Subject: [PATCH 39/53] Fix HMD look-at positions and lines --- interface/src/Application.cpp | 24 ++++++------------------ interface/src/avatar/MyAvatar.cpp | 22 +++++++++++----------- 2 files changed, 17 insertions(+), 29 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 23fc1f13e1..d8dad73d82 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2374,16 +2374,10 @@ void Application::updateMyAvatarLookAtPosition() { } else if (eyeTracker->isTracking() && (isHMDMode() || eyeTracker->isSimulating())) { // Look at the point that the user is looking at. if (isHMDMode()) { - - // TODO: Test ... - // Position of simulated look-at target should change with change in HMD position and orientation - glm::mat4 headPose = getActiveDisplayPlugin()->getHeadPose(); - glm::quat hmdOrientation = glm::quat_cast(headPose); - glm::vec3 hmdPosition = glm::vec3(headPose[3]); - - lookAtSpot = _myCamera.getPosition() + hmdPosition + - _myAvatar->getOrientation() * (hmdOrientation * eyeTracker->getLookAtPosition()); + glm::quat hmdRotation = glm::quat_cast(headPose); + lookAtSpot = _myCamera.getPosition() + + _myAvatar->getOrientation() * (hmdRotation * eyeTracker->getLookAtPosition()); } else { lookAtSpot = _myAvatar->getHead()->getEyePosition() + (_myAvatar->getHead()->getFinalOrientationInWorldFrame() * eyeTracker->getLookAtPosition()); @@ -2421,16 +2415,10 @@ void Application::updateMyAvatarLookAtPosition() { } else { // I am not looking at anyone else, so just look forward if (isHMDMode()) { - - // TODO: Test ... - // Look-at vector should go into distance based on HMD position and orientation - glm::mat4 headPose = getActiveDisplayPlugin()->getHeadPose(); - glm::quat hmdOrientation = glm::quat_cast(headPose); - glm::vec3 hmdPosition = glm::vec3(headPose[3]); - - lookAtSpot = _myCamera.getPosition() + hmdPosition + - _myAvatar->getOrientation() * (hmdOrientation * glm::vec3(0.0f, 0.0f, -TREE_SCALE)); + glm::quat headRotation = glm::quat_cast(headPose); + lookAtSpot = _myCamera.getPosition() + + _myAvatar->getOrientation() * (headRotation * glm::vec3(0.0f, 0.0f, -TREE_SCALE)); } else { lookAtSpot = _myAvatar->getHead()->getEyePosition() + (_myAvatar->getHead()->getFinalOrientationInWorldFrame() * glm::vec3(0.0f, 0.0f, -TREE_SCALE)); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 99973f2256..5578ba76bd 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1254,18 +1254,18 @@ void MyAvatar::renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, fl } if (qApp->isHMDMode()) { - - // TODO: Test ... - // Look-at vectors should go from HMD mid eyes regardless of combined position and orientation - - glm::mat4 leftEye = Application::getInstance()->getActiveDisplayPlugin()->getEyePose(Eye::Left); - glm::vec3 leftEyePosition = glm::vec3(leftEye[3]); - glm::mat4 rightEye = Application::getInstance()->getActiveDisplayPlugin()->getEyePose(Eye::Right); - glm::vec3 rightEyePosition = glm::vec3(leftEye[3]); - glm::vec3 cameraPosition = Application::getInstance()->getCamera()->getPosition(); - getHead()->renderLookAts(renderArgs, cameraPosition + leftEyePosition, - cameraPosition + rightEyePosition); + + glm::mat4 leftEyePose = Application::getInstance()->getActiveDisplayPlugin()->getEyePose(Eye::Left); + glm::vec3 leftEyePosition = glm::vec3(leftEyePose[3]); + glm::mat4 rightEyePose = Application::getInstance()->getActiveDisplayPlugin()->getEyePose(Eye::Right); + glm::vec3 rightEyePosition = glm::vec3(rightEyePose[3]); + glm::mat4 headPose = Application::getInstance()->getActiveDisplayPlugin()->getHeadPose(); + glm::vec3 headPosition = glm::vec3(headPose[3]); + + getHead()->renderLookAts(renderArgs, + cameraPosition + getOrientation() * (leftEyePosition - headPosition), + cameraPosition + getOrientation() * (rightEyePosition - headPosition)); } else { getHead()->renderLookAts(renderArgs); } From 8c47c7b9897620aaa4d45382969e623246f9e3a0 Mon Sep 17 00:00:00 2001 From: Seiji Emery Date: Tue, 11 Aug 2015 20:12:21 -0700 Subject: [PATCH 40/53] Added attachment --- examples/games/solarsystem.js | 189 +++++++++++--------------- examples/libraries/uiwidgets.js | 233 +++++++++++++++++++++++++------- 2 files changed, 265 insertions(+), 157 deletions(-) diff --git a/examples/games/solarsystem.js b/examples/games/solarsystem.js index 94e76ece08..0681ff19df 100644 --- a/examples/games/solarsystem.js +++ b/examples/games/solarsystem.js @@ -592,48 +592,89 @@ var ICON_HEIGHT = 40.0; var ICON_COLOR = UI.rgba(45, 45, 45, 0.7); var FOCUSED_COLOR = UI.rgba(250, 250, 250, 1.0); +var PANEL_BACKGROUND_COLOR = UI.rgba(50, 50, 50, 0.7); + +var PANEL_PADDING = 7.0; +var PANEL_BORDER = 12.0; var SUBPANEL_GAP = 1.0; + + + +var icons = []; function addIcon(panel, iconId) { - return panel.add(new UI.Icon({ + var icon = panel.add(new UI.Icon({ 'imageURL': ICONS_URL + iconId + '.svg', 'width': ICON_WIDTH, 'height': ICON_HEIGHT, 'color': ICON_COLOR, 'alpha': ICON_COLOR.a })); + icons.push(icon); + return icon; } -var panel = { - background: { - backgroundColor: UI.rgb(50, 50, 50), - backgroundAlpha: 0.7, - textColor: UI.rgb(10, 10, 20) - }, - padding: { x: 7, y: 7 }, - border: { x: 12, y: 12 } +var panels = []; +function addPanel (properties) { + properties.background = properties.background || {}; + properties.background.backgroundColor = properties.background.backgroundColor || + PANEL_BACKGROUND_COLOR; + properties.background.backgroundAlpha = properties.background.backgroundAlpha || + PANEL_BACKGROUND_COLOR.a; + properties.padding = properties.padding || { x: PANEL_PADDING, y: PANEL_PADDING }; + properties.border = properties.border || { x: PANEL_BORDER, y: PANEL_BORDER }; + + var panel = new UI.WidgetStack(properties); + panels.push(panel); + return panel; } // var panelContainer = new UI.WidgetContainer(); // panelContainer.setPosition(500, 250); // panelContainer.setVisible(true); -var mainPanel = new UI.WidgetStack({ - dir: '+y', - padding: panel.padding, - border: panel.border, - background: panel.background -}); -// panelContainer.add(mainPanel); +var mainPanel = addPanel({ dir: '+y' }); mainPanel.setPosition(500, 250); mainPanel.setVisible(true); - + var systemViewButton = addIcon(mainPanel, 'solarsystems'); var zoomButton = addIcon(mainPanel, 'magnifier'); var satelliteButton = addIcon(mainPanel, 'satellite'); var settingsButton = addIcon(mainPanel, 'settings'); var stopButton = addIcon(mainPanel, 'close'); -mainPanel.relayout(); + + +var systemViewPanel = addPanel({ dir: '+x', visible: false }); +var reverseButton = addIcon(systemViewPanel, 'reverse'); +var pauseButton = addIcon(systemViewPanel, 'playpause'); +var forwardButton = addIcon(systemViewPanel, 'forward'); + +var zoomPanel = addPanel({ dir: '+y', visible: true }); + +UI.updateLayout(); + +function hideSubpanels () { + systemViewPanel.setVisible(false); + zoomPanel.setVisible(false); +} + +function attachPanel (panel, button) { + button.addAction('onClick', function () { + var visible = !panel.visible; + hideSubpanels(); + panel.setVisible(visible); + UI.updateLayout(); + }) + + UI.addAttachment(panel, button, function (target, rel) { + target.setPosition( + rel.position.x - (target.cachedWidth + target.border.x + SUBPANEL_GAP), + rel.position.y - target.border.y + ); + }); +} +attachPanel(systemViewPanel, systemViewButton); +attachPanel(zoomPanel, zoomButton); var addColorToggle = function (widget) { widget.addAction('onMouseOver', function () { @@ -644,67 +685,32 @@ var addColorToggle = function (widget) { }); } -var systemViewPanel = new UI.WidgetStack({ - dir: '+x', - visible: true, - padding: panel.padding, - border: panel.border, - background: panel.background +reverseButton.addAction('onClick', function() { + +}); +pauseButton.addAction('onClick', function() { + // paused ? resume() : pause(); }); -UI.addAttachment({ - target: systemViewPanel, - rel: systemViewButton, - layout: function (target, rel) { - target.setPosition({ - x: rel.position - - }) +zoomButton.addAction('onClick', function() { + hideSubpanels(); + UI.updateLayout(); + + if (zoomButton.visible) { + MyAvatar.position = startingPosition; + Camera.setOrientation(cameraStart); + // resume(); + } else { + // pause(); } }); - -mainPanel.add({}); -// panelContainer.add(systemViewPanel); - -var reverseButton = addIcon(systemViewPanel, 'reverse'); -var pauseButton = addIcon(systemViewPanel, 'playpause'); -var forwardButton = addIcon(systemViewPanel, 'forward'); - - -var zoomPanel = new UI.WidgetStack({ - dir: '+y', - visible: false, - padding: panel.padding, - border: panel.border, - background: panel.background -}); -// panelContainer.add(zoomPanel); - - UI.updateLayout(); -function hideSubpanels () { - systemViewPanel.setVisible(false); - zoomPanel.setVisible(false); -} -function attachTogglePanel (button, panel) { - button.addAction('onClick', function () { - var visible = !panel.visible; - - hideSubpanels(); - panel.setVisible(true); - panel.relayout(); - var offset = { - 'x': -(panel.getWidth() + panel.border.x + SUBPANEL_GAP), - 'y': -panel.border.y - }; - - panel.setPosition(Vec2.sum(button.position, offset)); - panel.setVisible(false); // force dirty - panel.setVisible(visible); - UI.updateLayout(); - }) -} +stopButton.addAction('onClick', function() { + // Script.stop(); + teardown(); +}); // Panel drag behavior // (click + drag on border to drag) @@ -745,47 +751,12 @@ function makeDraggable (panel, target) { }); } -var buttons = [ systemViewButton, zoomButton, satelliteButton, settingsButton, stopButton, - reverseButton, pauseButton, forwardButton ]; -var panels = [ mainPanel, systemViewPanel, zoomPanel ]; +var buttons = icons; buttons.map(addColorToggle); -panels.map(function (panel) { makeDraggable(panel, panelContainer); }); +panels.map(function (panel) { makeDraggable(panel, mainPanel); }); -attachTogglePanel(systemViewButton, systemViewPanel); -// systemViewButton.addAction('onMouseDown', function () { - // systemViewPanel.setVisible(true); - // UI.updateLayout(); -// }); - -reverseButton.addAction('onClick', function() { - -}); -pauseButton.addAction('onClick', function() { - // paused ? resume() : pause(); -}); - -zoomButton.addAction('onClick', function() { - hideSubpanels(); - UI.updateLayout(); - - if (zoomButton.visible) { - MyAvatar.position = startingPosition; - Camera.setOrientation(cameraStart); - // resume(); - } else { - // pause(); - } -}); -attachTogglePanel(zoomButton, zoomPanel); -UI.updateLayout(); - - -stopButton.addAction('onClick', function() { - // Script.stop(); - teardown(); -}); // Script.include('../utilities/tools/cookies.js'); diff --git a/examples/libraries/uiwidgets.js b/examples/libraries/uiwidgets.js index 129675b02c..0e53e9fb18 100644 --- a/examples/libraries/uiwidgets.js +++ b/examples/libraries/uiwidgets.js @@ -157,7 +157,7 @@ UI.setDefaultVisibility = function (visible) { // Hide overlays impl function makeOverlay(type, properties) { - var _TRACE = traceEnter.call(this, "makeOverlay"); + var _TRACE = traceEnter.call(this, "makeOverlay"); var overlay = Overlays.addOverlay(type, properties); // overlay.update = function (properties) { // Overlays.editOverlay(overlay, properties); @@ -554,57 +554,188 @@ Icon.prototype.setColor = function (color) { }); } -var Attachment = UI.Attachment = function (target, impl) { - Widget.prototype.constructor.apply(this); - this.rel = { - x: target.x - this.position.x, - y: target.y - this.position.y - }; -} -Attachment.prototype.setDirty = function (layout) { - this._dirty = true; - this.target.setDirty(); -} -Attachment.prototype.relayout = function (layout) { - targetPos = Vec2.sum(this.position, this.target.position); -} - -// UI.updateLayout = function () { -// var touched = {}; - -// function recalcDimensions(widget) { -// if (widget.parent && !touched[widget.parent.id]) -// recalcDimensions(widget.parent); -// touched[widget.id] = true; -// widget.cachedWidth = widget.calcWidth(); -// widget.cachedHeight = widget.calcHeight(); -// } -// widgetList.forEach(function (widget) { -// if (!touched[widget.id]) { -// recalcDimensions(widget); -// } -// }); +// var Attachment = UI.Attachment = function (target, impl) { +// Widget.prototype.constructor.apply(this); +// this.rel = { +// x: target.x - this.position.x, +// y: target.y - this.position.y +// }; // } +// Attachment.prototype.setDirty = function (layout) { +// this._dirty = true; +// this.target.setDirty(); +// } +// Attachment.prototype.relayout = function (layout) { +// targetPos = Vec2.sum(this.position, this.target.position); +// } + + +// New layout functions +Widget.prototype.calcWidth = function () { + return 0; +}; +Widget.prototype.calcHeight = function () { + return 0; +}; +Widget.prototype.applyLayout = function () {}; +Widget.prototype.updateOverlays = function () {}; + + +Icon.prototype.calcWidth = function () { + return this.width; +} +Icon.prototype.calcHeight = function () { + return this.height; +} +Icon.prototype.updateOverlays = function () { + this.iconOverlay.update({ + width: this.width, + height: this.height, + x: this.position.x, + y: this.position.y, + visible: this.isVisible() + }); +} + +var sumOf = function (list, f) { + var sum = 0.0; + list.forEach(function (elem) { + sum += f(elem); + }) + return sum; +} + +WidgetStack.prototype.calcWidth = function () { + var totalWidth = 0.0, maxWidth = 0.0; + this.widgets.forEach(function (widget) { + totalWidth += widget.calcWidth() + this.padding.x; + maxWidth = Math.max(maxWidth, widget.calcWidth()); + }, this); + return this.border.x * 2 + + Math.max(totalWidth * this.layoutDir.x - this.padding.x, maxWidth); +} +WidgetStack.prototype.calcHeight = function () { + var totalHeight = 0.0, maxHeight = 0.0; + this.widgets.forEach(function (widget) { + totalHeight += widget.calcHeight() + this.padding.y; + maxHeight = Math.max(maxHeight, widget.calcHeight()); + }, this); + return this.border.y * 2 + + Math.max(totalHeight * this.layoutDir.y - this.padding.y, maxHeight); +} +WidgetStack.prototype.applyLayout = function () { + print("Applying layout " + this); + var x = this.position.x + this.border.x; + var y = this.position.y + this.border.y; + + this.widgets.forEach(function (widget) { + widget.setPosition(x, y); + print("setting position for " + widget + ": " + x + ", " + y) + x += (widget.cachedWidth + this.padding.x) * this.layoutDir.x; + y += (widget.cachedHeight + this.padding.y) * this.layoutDir.y; + widget._parentVisible = this.isVisible(); + }, this); +} +WidgetStack.prototype.updateOverlays = function () { + this.backgroundOverlay.update({ + width: this.cachedWidth, + height: this.cachedHeight, + x: this.position.x, + y: this.position.y, + visible: this.isVisible() + }); +} +UI.addAttachment = function (target, rel, update) { + attachment = { + target: target, + rel: rel, + applyLayout: update + }; + ui.attachmentList.push(attachment); + return attachment; +} + + +UI.updateLayout = function () { + // Recalc dimensions + var touched = {}; + function recalcDimensions(widget) { + if (widget.parent && !touched[widget.parent.id]) + recalcDimensions(widget.parent); + touched[widget.id] = true; + widget.cachedWidth = widget.calcWidth(); + widget.cachedHeight = widget.calcHeight(); + } + ui.widgetList.forEach(function (widget) { + if (!touched[widget.id]) { + recalcDimensions(widget); + } + }); + + function insertAndPush (list, index, elem) { + if (list[index]) + list[index].push(elem); + else + list[index] = [ elem ]; + } + + // Generate attachment lookup + var attachmentDeps = {}; + ui.attachmentList.forEach(function(attachment) { + insertAndPush(attachmentDeps, attachment.target.id, { + dep: attachment.rel, + eval: attachment.applyLayout + }); + }); + updated = {}; + + // Walk the widget list and relayout everything + function recalcLayout (widget) { + // Short circuit if we've already updated + if (updated[widget.id]) + return; + + // Walk up the tree + update top level first + if (widget.parent) + recalcLayout(widget.parent); + + // Resolve and apply attachment dependencies + if (attachmentDeps[widget.id]) { + attachmentDeps[widget.id].forEach(function (attachment) { + recalcLayout(attachment.dep); + attachment.eval(widget, attachment.dep); + }); + } + + widget.applyLayout(); + updated[widget.id] = true; + } + ui.widgetList.forEach(recalcLayout); + + ui.widgetList.forEach(function (widget) { + widget.updateOverlays(); + }); +} UI.setDefaultVisibility = function(visibility) { ui.defaultVisible = visibility; }; -UI.updateLayout = function () { - var _TRACE = traceEnter.call(this, "UI.updateLayout()"); - ui.widgetList.forEach(function(widget, index, array) { - assert(typeof(widget) == 'object', 'typeof(widget) == "object"'); - if (widget._dirty) { - var top = widget; - while (top.parent && top.parent._dirty) - top = top.parent; - top.relayout(); - if (widget !== top && widget._dirty) - widget.relayout(); - assert(!widget._dirty, "widget._dirty == false"); - } - }); - _TRACE.exit(); -}; +// UI.updateLayout = function () { +// var _TRACE = traceEnter.call(this, "UI.updateLayout()"); +// ui.widgetList.forEach(function(widget, index, array) { +// assert(typeof(widget) == 'object', 'typeof(widget) == "object"'); +// if (widget._dirty) { +// var top = widget; +// while (top.parent && top.parent._dirty) +// top = top.parent; +// top.relayout(); +// if (widget !== top && widget._dirty) +// widget.relayout(); +// assert(!widget._dirty, "widget._dirty == false"); +// } +// }); +// _TRACE.exit(); +// }; function dispatchEvent(actions, widget, event) { var _TRACE = traceEnter.call(this, "UI.dispatchEvent()"); @@ -615,6 +746,7 @@ function dispatchEvent(actions, widget, event) { } ui.focusedWidget = null; +ui.clickedWidget = null; // ui.selectedWidget = null; var getWidgetWithOverlay = function (overlay) { @@ -678,6 +810,7 @@ UI.handleMousePress = function (event) { print("Mouse clicked"); UI.handleMouseMove(event); if (ui.focusedWidget) { + ui.clickedWidget = ui.focusedWidget; dispatchEvent('onMouseDown', event, ui.focusedWidget); } } @@ -687,7 +820,11 @@ UI.handleMouseRelease = function (event) { UI.handleMouseMove(event); if (ui.focusedWidget) { dispatchEvent('onMouseUp', event, ui.focusedWidget); - dispatchEvent('onClick', event, ui.focusedWidget); + + if (ui.clickedWidget == ui.focusedWidget) { + dispatchEvent('onClick', event, ui.focusedWidget); + } + ui.clickedWidget = null;; } } From 8f376247f069b9503d1f6aa9b104ea508f0d5b44 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 11 Aug 2015 20:25:02 -0700 Subject: [PATCH 41/53] Fix calculation of corrected look-at position in HMD view --- interface/src/avatar/MyAvatar.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 5578ba76bd..6bd65701d3 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -935,7 +935,7 @@ void MyAvatar::updateLookAtTargetAvatar() { if (Application::getInstance()->isHMDMode()) { avatar->getHead()->setCorrectedLookAtPosition(Application::getInstance()->getViewFrustum()->getPosition() - + glm::vec3(qApp->getHMDSensorPose()[3]) + gazeOffset); + + gazeOffset); } else { avatar->getHead()->setCorrectedLookAtPosition(Application::getInstance()->getViewFrustum()->getPosition() + gazeOffset); From f247f0390c68f5549b202532d3c8e7d0d652c502 Mon Sep 17 00:00:00 2001 From: Seiji Emery Date: Tue, 11 Aug 2015 21:56:00 -0700 Subject: [PATCH 42/53] Cleanup + refactoring --- examples/games/solarsystem.js | 12 +- examples/libraries/uiwidgets.js | 507 +++++++++++++++----------------- 2 files changed, 245 insertions(+), 274 deletions(-) diff --git a/examples/games/solarsystem.js b/examples/games/solarsystem.js index 0681ff19df..5758ae4fc7 100644 --- a/examples/games/solarsystem.js +++ b/examples/games/solarsystem.js @@ -660,7 +660,7 @@ function hideSubpanels () { function attachPanel (panel, button) { button.addAction('onClick', function () { - var visible = !panel.visible; + var visible = !panel.isVisible(); hideSubpanels(); panel.setVisible(visible); UI.updateLayout(); @@ -668,7 +668,7 @@ function attachPanel (panel, button) { UI.addAttachment(panel, button, function (target, rel) { target.setPosition( - rel.position.x - (target.cachedWidth + target.border.x + SUBPANEL_GAP), + rel.position.x - (target.getWidth() + target.border.x + SUBPANEL_GAP), rel.position.y - target.border.y ); }); @@ -741,10 +741,10 @@ function makeDraggable (panel, target) { var initialPos = { x: target.position.x, y: target.position.y }; startDrag({ updateDrag: function (event) { - target.setPosition({ - x: initialPos.x + event.x - dragStart.x, - y: initialPos.y + event.y - dragStart.y - }); + target.setPosition( + initialPos.x + event.x - dragStart.x, + initialPos.y + event.y - dragStart.y + ); UI.updateLayout(); } }); diff --git a/examples/libraries/uiwidgets.js b/examples/libraries/uiwidgets.js index 0e53e9fb18..e5d6bb68d7 100644 --- a/examples/libraries/uiwidgets.js +++ b/examples/libraries/uiwidgets.js @@ -104,7 +104,7 @@ var traceEnter = function(fcn) { }; } -// UI namespace +/// UI namespace var UI = this.UI = {}; var rgb = UI.rgb = function (r, g, b) { @@ -128,7 +128,7 @@ var rgba = UI.rgba = function (r, g, b, a) { return { 'r': r || 0, 'g': g || 0, 'b': b || 0, 'a': a }; } -// Protected UI state +/// Protected UI state var ui = { defaultVisible: true, widgetList: new Array(), @@ -155,7 +155,7 @@ UI.setDefaultVisibility = function (visible) { ui.defaultVisible = visible; } -// Hide overlays impl +/// Wrapper around the overlays impl function makeOverlay(type, properties) { var _TRACE = traceEnter.call(this, "makeOverlay"); var overlay = Overlays.addOverlay(type, properties); @@ -187,119 +187,128 @@ function makeOverlay(type, properties) { var COLOR_WHITE = rgb(255, 255, 255); var COLOR_GRAY = rgb(125, 125, 125); -// Widget base class +/// Base widget class. var Widget = function () {}; -__widgetId = 0; - +// Shared methods: +var __widgetId = 0; Widget.prototype.constructor = function () { - var _TRACE = traceEnter.call(this, "Widget.constructor()"); this.position = { 'x': 0.0, 'y': 0.0 }; + this.dimensions = null; + this.visible = ui.defaultVisible; + this.parentVisible = null; + this.actions = {}; this._dirty = true; this.parent = null; - this._parentVisible = true; + this.id = __widgetId++; ui.widgetList.push(this); - assert(this.position, "this.position != undefined"); - _TRACE.exit(); } - -Widget.prototype.setPosition = function () { - var _TRACE = traceEnter.call(this, "Widget.setPosition()"); - if (arguments.length == 1) { - (function (pos) { - this.position = { x: pos.x, y: pos.y }; - }).apply(this, arguments); +Widget.prototype.setPosition = function (x, y) { + if (arguments.length == 1 && typeof(arguments[0]) == 'object') { + x = arguments[0].x; + y = arguments[0].y; + } + if (typeof(x) != 'number' || typeof(y) != 'number') { + ui.complain("invalid arguments to " + this + ".setPosition: '" + arguments + "' (expected (x, y) or (vec2))"); } else { - (function (x, y) { - this.position = { x: x, y: y }; - }).apply(this, arguments); + this.position.x = x; + this.position.y = y; } - this.setDirty(); - _TRACE.exit(); } -Widget.prototype.setVisible = function(visible) { - var _TRACE = traceEnter.call(this, "Widget.setVisible()"); - if (this.visible != visible) { - this.setDirty(); - } +Widget.prototype.setVisible = function (visible) { this.visible = visible; - _TRACE.exit(); + this.parentVisible = null; // set dirty } Widget.prototype.isVisible = function () { - return this.visible && this._parentVisible; + if (this.parentVisible === null) + this.parentVisible = this.parent ? this.parent.isVisible() : true; + return this.visible && this.parentVisible; +} +// Store lists of actions (multiple callbacks per key) +Widget.prototype.addAction = function (action, callback) { + if (!this.actions[action]) + this.actions[action] = [ callback ]; + else + this.actions[action].push(callback); +} +Widget.prototype.clearLayout = function () { + this.dimensions = null; + this.parentVisible = null; +} +// Overridden methods: +Widget.prototype.toString = function () { + return "[Widget " + this.id + " ]"; } Widget.prototype.getOverlay = function () { return null; } -Widget.prototype.addAction = function (name, callback) { - var _TRACE = traceEnter.call(this, "Widget.addAction()"); - if (!this.actions[name]) { - this.actions[name] = [ callback ]; - } else { - this.actions[name].push(callback); - } - _TRACE.exit(); -} -Widget.prototype.setDirty = function () { - this._dirty = true; -} - -var __first = 0; -Widget.prototype.toString = function () { - // var _TRACE = (__trace[__trace.length - 1] != "Widget.toString()") && __first++ == 0 ? - // traceEnter.call(this, "Widget.toString()") : { 'exit': function(){} }; - // assert(this.position, "this.position != undefined"); - // _TRACE.exit(); - return "[Widget " + this.id + " ]"//+" (pos=(" + this.position.x + ", " + this.position.y + "), parent=" + this.parent + ")"; -} - -var WidgetContainer = UI.WidgetContainer = function () { - Widget.prototype.constructor.call(this); - this.children = new Array(); - this._lastPos = { x: this.position.x, y: this.position.y }; -}; -WidgetContainer.prototype = new Widget(); -WidgetContainer.prototype.constructor = WidgetContainer; - -// WidgetContainer.prototype.setPosition = function (pos) { -// if (arguments.length > 1) -// pos = { x: arguments[0], y: arguments[1] }; -// this.newPos = pos; -// this.setDirty(); -// } - -WidgetContainer.prototype.add = function (child) { - this.children.push(child); - child.parent = this; - return child; -} -WidgetContainer.prototype.relayout = function (layout) { - var rx = this.position.x - this._lastPos.x; - var ry = this.position.y - this._lastPos.y; - if (rx || ry) { - this.children.forEach(function (child) { - child.setPosition(child.position.x + rx, child.position.y + ry); - child.relayout(layout); - }, this); - } - this._lastPos.x = this.position.x; - this._lastPos.y = this.position.y; - this._dirty = false; -} -WidgetContainer.prototype.getWidth = function () { +Widget.prototype.getWidth = function () { return 0; } -WidgetContainer.prototype.getHeight = function () { +Widget.prototype.getHeight = function () { return 0; } -WidgetContainer.prototype.hasOverlay = function () { +Widget.prototype.hasOverlay = function () { return false; } -// Inherits from Widget + +// var WidgetContainer = UI.WidgetContainer = function () { +// Widget.prototype.constructor.call(this); +// this.children = new Array(); +// this._lastPos = { x: this.position.x, y: this.position.y }; +// }; +// WidgetContainer.prototype = new Widget(); +// WidgetContainer.prototype.constructor = WidgetContainer; + +// WidgetContainer.prototype.add = function (child) { +// this.children.push(child); +// child.parent = this; +// return child; +// } +// WidgetContainer.prototype.relayout = function (layout) { +// var rx = this.position.x - this._lastPos.x; +// var ry = this.position.y - this._lastPos.y; +// if (rx || ry) { +// this.children.forEach(function (child) { +// child.setPosition(child.position.x + rx, child.position.y + ry); +// child.relayout(layout); +// }, this); +// } +// this._lastPos.x = this.position.x; +// this._lastPos.y = this.position.y; +// this._dirty = false; +// } +// WidgetContainer.prototype.getWidth = function () { +// return 0; +// } +// WidgetContainer.prototype.getHeight = function () { +// return 0; +// } +// WidgetContainer.prototype.hasOverlay = function () { +// return false; +// } + + +/// Implements a simple auto-layouted container of methods. +/// @param properties +/// dir: [string] +/// layout direction. +/// Can be one of [ '+x', '+y', '-x', '-y' ] for 2d directions. +/// border: { x: _, y: _ } +/// Adds spacing to the widget on all sides (aka. margin). Defaults to 0. +/// padding: { x: _, y: _ } +/// Padding in between each widget. Only one axis is used (the layout direction). +/// visible: true | false +/// Acts as both a widget (logical) property and is used for overlays. +/// Hiding this will hide all child widgets (non-destructively). +/// Do not access this directly -- use setVisible(value) and isVisible() instead. +/// background: [object] +/// Properties to use for the background overlay (if defined). +/// var WidgetStack = UI.WidgetStack = function (properties) { var _TRACE = traceEnter.call(this, "WidgetStack.constructor()"); Widget.prototype.constructor.call(this); @@ -335,6 +344,8 @@ var WidgetStack = UI.WidgetStack = function (properties) { background.alpha = background.alpha || 1.0; background.visible = this.visible; this.backgroundOverlay = makeOverlay("text", background); + } else { + this.backgroundOverlay = null; } this.widgets = new Array(); @@ -349,132 +360,118 @@ WidgetStack.prototype.toString = function () { } WidgetStack.prototype.add = function (widget) { - var _TRACE = traceEnter.call(this, "WidgetStack.add()"); this.widgets.push(widget); widget.parent = this; - this.setDirty(); - - assert(this.widgets, "this.widgets != undefined"); - - _TRACE.exit(); return widget; } -WidgetStack.prototype.relayout = function (layoutFrom) { - var _TRACE = traceEnter.call(this, "WidgetStack.relayout()"); - // print("this = " + this); - // print("this.position = { " + this.position.x + ", " + this.position.y + " }") +// WidgetStack.prototype.relayout = function (layoutFrom) { +// var _TRACE = traceEnter.call(this, "WidgetStack.relayout()"); +// // print("this = " + this); +// // print("this.position = { " + this.position.x + ", " + this.position.y + " }") - // this.position.x += 500; +// // this.position.x += 500; - // this.setPosition({ - // x: this.position.x + 5, - // y: this.position.y - // }); +// // this.setPosition({ +// // x: this.position.x + 5, +// // y: this.position.y +// // }); - var layoutStart = { - 'x': this.position.x + this.border.x, - 'y': this.position.y + this.border.y - } +// var layoutStart = { +// 'x': this.position.x + this.border.x, +// 'y': this.position.y + this.border.y +// } - var layout = { - 'pos': Vec2.clone(layoutStart), - 'bounds': new Rect(this.position.x, this.position.y, this.position.x, this.position.y) - }; - // print("this.widgets = " + this.widgets); - assert(this.widgets, "this.widgets != undefined"); +// var layout = { +// 'pos': Vec2.clone(layoutStart), +// 'bounds': new Rect(this.position.x, this.position.y, this.position.x, this.position.y) +// }; +// // print("this.widgets = " + this.widgets); +// assert(this.widgets, "this.widgets != undefined"); - if (this.parent == null) - this._parentVisible = true; - var this_isVisible = this.isVisible(); +// if (this.parent == null) +// this._parentVisible = true; +// var this_isVisible = this.isVisible(); - this.widgets.forEach(function (child, i, elems) { - child.setPosition(layout.pos.x, layout.pos.y); - child._parentVisible = this_isVisible; - child.relayout(layout); +// this.widgets.forEach(function (child, i, elems) { +// child.setPosition(layout.pos.x, layout.pos.y); +// child._parentVisible = this_isVisible; +// child.relayout(layout); - if (child.isVisible()) { - layout.bounds.grow({ - 'x': layout.pos.x + child.getWidth(), - 'y': layout.pos.y + child.getHeight() - }); - layout.pos.x += (child.getWidth() + this.padding.x) * this.layoutDir.x; - layout.pos.y += (child.getHeight() + this.padding.y) * this.layoutDir.y; - } - // elems[i] = child; - }, this); +// if (child.isVisible()) { +// layout.bounds.grow({ +// 'x': layout.pos.x + child.getWidth(), +// 'y': layout.pos.y + child.getHeight() +// }); +// layout.pos.x += (child.getWidth() + this.padding.x) * this.layoutDir.x; +// layout.pos.y += (child.getHeight() + this.padding.y) * this.layoutDir.y; +// } +// // elems[i] = child; +// }, this); - this._dirty = false; +// this._dirty = false; - layout.bounds.grow(Vec2.sum(layout.bounds.getBtmRight(), this.border)); - layout.pos.x += this.border.x * this.layoutDir.x; - layout.pos.y += this.border.y * this.layoutDir.y; +// layout.bounds.grow(Vec2.sum(layout.bounds.getBtmRight(), this.border)); +// layout.pos.x += this.border.x * this.layoutDir.x; +// layout.pos.y += this.border.y * this.layoutDir.y; - this._calculatedDimensions = { - 'x': layout.bounds.getWidth(), - 'y': layout.bounds.getHeight() - }; - if (this.backgroundOverlay) { - this.backgroundOverlay.update({ - 'x': this.position.x,// - this.border.x, - 'y': this.position.y,// - this.border.y, - 'width': this._calculatedDimensions.x, - 'height': this._calculatedDimensions.y, - 'visible': this.visible - }); - } - print("RELAYOUT " + this); - print("layoutDir: " + this.layoutDir.x + ", " + this.layoutDir.y); - print("padding: " + this.padding.x + ", " + this.padding.y); - print("topleft: " + layout.bounds.getTopLeft().x + ", " + layout.bounds.getTopLeft().y); - print("btmright: " + layout.bounds.getBtmRight().x + ", " + layout.bounds.getBtmRight().y); - print("width, height = " + layout.bounds.getWidth() + ", " + layout.bounds.getHeight()); - print("start: " + this.position.x + ", " + this.position.y); - print("end: " + layout.pos.x + ", " + layout.pos.y); +// this._calculatedDimensions = { +// 'x': layout.bounds.getWidth(), +// 'y': layout.bounds.getHeight() +// }; +// if (this.backgroundOverlay) { +// this.backgroundOverlay.update({ +// 'x': this.position.x,// - this.border.x, +// 'y': this.position.y,// - this.border.y, +// 'width': this._calculatedDimensions.x, +// 'height': this._calculatedDimensions.y, +// 'visible': this.visible +// }); +// } +// print("RELAYOUT " + this); +// print("layoutDir: " + this.layoutDir.x + ", " + this.layoutDir.y); +// print("padding: " + this.padding.x + ", " + this.padding.y); +// print("topleft: " + layout.bounds.getTopLeft().x + ", " + layout.bounds.getTopLeft().y); +// print("btmright: " + layout.bounds.getBtmRight().x + ", " + layout.bounds.getBtmRight().y); +// print("width, height = " + layout.bounds.getWidth() + ", " + layout.bounds.getHeight()); +// print("start: " + this.position.x + ", " + this.position.y); +// print("end: " + layout.pos.x + ", " + layout.pos.y); - if (layoutFrom) { - layoutFrom.pos = layout.pos; - layoutFrom.bounds.grow(layout.getTopLeft()); - layoutFrom.bounds.grow(layout.getBtmRight()); - } - _TRACE.exit(); -} -WidgetStack.prototype.getWidth = function () { - var _TRACE = traceEnter.call(this, "WidgetStack.getWidth()"); - if (this._dirty || !this._calculatedDimensions) - this.relayout(); - _TRACE.exit(); - return this._calculatedDimensions.x; -} -WidgetStack.prototype.getHeight = function () { - var _TRACE = traceEnter.call(this, "WidgetStack.getHeight()"); - if (this._dirty || !this._calculatedDimensions) - this.relayout(); - _TRACE.exit(); - return this._calculatedDimensions.y; -} -WidgetStack.prototype.show = function () { - this.relayout(); -} +// if (layoutFrom) { +// layoutFrom.pos = layout.pos; +// layoutFrom.bounds.grow(layout.getTopLeft()); +// layoutFrom.bounds.grow(layout.getBtmRight()); +// } +// _TRACE.exit(); +// } +// WidgetStack.prototype.getWidth = function () { +// var _TRACE = traceEnter.call(this, "WidgetStack.getWidth()"); +// if (this._dirty || !this._calculatedDimensions) +// this.relayout(); +// _TRACE.exit(); +// return this._calculatedDimensions.x; +// } +// WidgetStack.prototype.getHeight = function () { +// var _TRACE = traceEnter.call(this, "WidgetStack.getHeight()"); +// if (this._dirty || !this._calculatedDimensions) +// this.relayout(); +// _TRACE.exit(); +// return this._calculatedDimensions.y; +// } +// WidgetStack.prototype.show = function () { +// this.relayout(); +// } WidgetStack.prototype.hasOverlay = function (overlayId) { return this.backgroundOverlay && this.backgroundOverlay.getId() === overlayId; } WidgetStack.prototype.getOverlay = function () { return this.backgroundOverlay; } -WidgetStack.prototype.setDirty = function () { - this._dirty = true; - this.widgets.forEach(function(widget){ - widget.setDirty(); - }); -} WidgetStack.prototype.destroy = function () { - var _TRACE = traceEnter.call(this, "WidgetStack.destroy()"); if (this.backgroundOverlay) { this.backgroundOverlay.destroy(); this.backgroundOverlay = null; } - _TRACE.exit(); } WidgetStack.prototype.setColor = function (color) { if (arguments.length != 1) { @@ -513,18 +510,18 @@ Icon.prototype.toString = function () { return "[UI.Icon " + this.id + " ]"; } -Icon.prototype.relayout = function (layout) { - var _TRACE = traceEnter.call(this, "Icon.relayout()"); - this._dirty = false; - this.iconOverlay.update({ - 'x': this.position.x, - 'y': this.position.y, - 'width': this.width, - 'height': this.height, - 'visible': this.visible - }); - _TRACE.exit() -} +// Icon.prototype.relayout = function (layout) { +// var _TRACE = traceEnter.call(this, "Icon.relayout()"); +// this._dirty = false; +// this.iconOverlay.update({ +// 'x': this.position.x, +// 'y': this.position.y, +// 'width': this.width, +// 'height': this.height, +// 'visible': this.visible +// }); +// _TRACE.exit() +// } Icon.prototype.getHeight = function () { return this.height; } @@ -554,37 +551,15 @@ Icon.prototype.setColor = function (color) { }); } -// var Attachment = UI.Attachment = function (target, impl) { -// Widget.prototype.constructor.apply(this); -// this.rel = { -// x: target.x - this.position.x, -// y: target.y - this.position.y -// }; -// } -// Attachment.prototype.setDirty = function (layout) { -// this._dirty = true; -// this.target.setDirty(); -// } -// Attachment.prototype.relayout = function (layout) { -// targetPos = Vec2.sum(this.position, this.target.position); -// } - - // New layout functions -Widget.prototype.calcWidth = function () { - return 0; -}; -Widget.prototype.calcHeight = function () { - return 0; -}; Widget.prototype.applyLayout = function () {}; Widget.prototype.updateOverlays = function () {}; -Icon.prototype.calcWidth = function () { +Icon.prototype.getWidth = function () { return this.width; } -Icon.prototype.calcHeight = function () { +Icon.prototype.getHeight = function () { return this.height; } Icon.prototype.updateOverlays = function () { @@ -604,24 +579,31 @@ var sumOf = function (list, f) { }) return sum; } - -WidgetStack.prototype.calcWidth = function () { +WidgetStack.prototype.calculateDimensions = function () { var totalWidth = 0.0, maxWidth = 0.0; - this.widgets.forEach(function (widget) { - totalWidth += widget.calcWidth() + this.padding.x; - maxWidth = Math.max(maxWidth, widget.calcWidth()); - }, this); - return this.border.x * 2 + - Math.max(totalWidth * this.layoutDir.x - this.padding.x, maxWidth); -} -WidgetStack.prototype.calcHeight = function () { var totalHeight = 0.0, maxHeight = 0.0; this.widgets.forEach(function (widget) { - totalHeight += widget.calcHeight() + this.padding.y; - maxHeight = Math.max(maxHeight, widget.calcHeight()); + totalWidth += widget.getWidth() + this.padding.x; + maxWidth = Math.max(maxWidth, widget.getWidth()); + + totalHeight += widget.getHeight() + this.padding.y; + maxHeight = Math.max(maxHeight, widget.getHeight()); }, this); - return this.border.y * 2 + - Math.max(totalHeight * this.layoutDir.y - this.padding.y, maxHeight); + + this.dimensions = { + x: this.border.x * 2 + Math.max(totalWidth * this.layoutDir.x - this.padding.x, maxWidth), + y: this.border.y * 2 + Math.max(totalHeight * this.layoutDir.y - this.padding.y, maxHeight) + }; +} +WidgetStack.prototype.getWidth = function () { + if (!this.dimensions) + this.calculateDimensions(); + return this.dimensions.x; +} +WidgetStack.prototype.getHeight = function () { + if (!this.dimensions) + this.calculateDimensions(); + return this.dimensions.y; } WidgetStack.prototype.applyLayout = function () { print("Applying layout " + this); @@ -631,15 +613,15 @@ WidgetStack.prototype.applyLayout = function () { this.widgets.forEach(function (widget) { widget.setPosition(x, y); print("setting position for " + widget + ": " + x + ", " + y) - x += (widget.cachedWidth + this.padding.x) * this.layoutDir.x; - y += (widget.cachedHeight + this.padding.y) * this.layoutDir.y; + x += (widget.getWidth() + this.padding.x) * this.layoutDir.x; + y += (widget.getHeight() + this.padding.y) * this.layoutDir.y; widget._parentVisible = this.isVisible(); }, this); } WidgetStack.prototype.updateOverlays = function () { this.backgroundOverlay.update({ - width: this.cachedWidth, - height: this.cachedHeight, + width: this.getWidth(), + height: this.getHeight(), x: this.position.x, y: this.position.y, visible: this.isVisible() @@ -658,18 +640,23 @@ UI.addAttachment = function (target, rel, update) { UI.updateLayout = function () { // Recalc dimensions - var touched = {}; - function recalcDimensions(widget) { - if (widget.parent && !touched[widget.parent.id]) - recalcDimensions(widget.parent); - touched[widget.id] = true; - widget.cachedWidth = widget.calcWidth(); - widget.cachedHeight = widget.calcHeight(); - } + // var touched = {}; + // function recalcDimensions(widget) { + // if (widget.parent && !touched[widget.parent.id]) + // recalcDimensions(widget.parent); + // touched[widget.id] = true; + // widget.cachedWidth = widget.calcWidth(); + // widget.cachedHeight = widget.calcHeight(); + // } + // ui.widgetList.forEach(function (widget) { + // if (!touched[widget.id]) { + // recalcDimensions(widget); + // } + // }); + + // Do this lazily instead ui.widgetList.forEach(function (widget) { - if (!touched[widget.id]) { - recalcDimensions(widget); - } + widget.clearLayout(); }); function insertAndPush (list, index, elem) { @@ -720,22 +707,6 @@ UI.updateLayout = function () { UI.setDefaultVisibility = function(visibility) { ui.defaultVisible = visibility; }; -// UI.updateLayout = function () { -// var _TRACE = traceEnter.call(this, "UI.updateLayout()"); -// ui.widgetList.forEach(function(widget, index, array) { -// assert(typeof(widget) == 'object', 'typeof(widget) == "object"'); -// if (widget._dirty) { -// var top = widget; -// while (top.parent && top.parent._dirty) -// top = top.parent; -// top.relayout(); -// if (widget !== top && widget._dirty) -// widget.relayout(); -// assert(!widget._dirty, "widget._dirty == false"); -// } -// }); -// _TRACE.exit(); -// }; function dispatchEvent(actions, widget, event) { var _TRACE = traceEnter.call(this, "UI.dispatchEvent()"); From 986a821c918c444ee72267cac592f577412f0295 Mon Sep 17 00:00:00 2001 From: Seiji Emery Date: Tue, 11 Aug 2015 22:00:53 -0700 Subject: [PATCH 43/53] cleanup --- examples/libraries/uiwidgets.js | 214 -------------------------------- 1 file changed, 214 deletions(-) diff --git a/examples/libraries/uiwidgets.js b/examples/libraries/uiwidgets.js index e5d6bb68d7..7f1f79645e 100644 --- a/examples/libraries/uiwidgets.js +++ b/examples/libraries/uiwidgets.js @@ -255,44 +255,6 @@ Widget.prototype.hasOverlay = function () { return false; } - -// var WidgetContainer = UI.WidgetContainer = function () { -// Widget.prototype.constructor.call(this); -// this.children = new Array(); -// this._lastPos = { x: this.position.x, y: this.position.y }; -// }; -// WidgetContainer.prototype = new Widget(); -// WidgetContainer.prototype.constructor = WidgetContainer; - -// WidgetContainer.prototype.add = function (child) { -// this.children.push(child); -// child.parent = this; -// return child; -// } -// WidgetContainer.prototype.relayout = function (layout) { -// var rx = this.position.x - this._lastPos.x; -// var ry = this.position.y - this._lastPos.y; -// if (rx || ry) { -// this.children.forEach(function (child) { -// child.setPosition(child.position.x + rx, child.position.y + ry); -// child.relayout(layout); -// }, this); -// } -// this._lastPos.x = this.position.x; -// this._lastPos.y = this.position.y; -// this._dirty = false; -// } -// WidgetContainer.prototype.getWidth = function () { -// return 0; -// } -// WidgetContainer.prototype.getHeight = function () { -// return 0; -// } -// WidgetContainer.prototype.hasOverlay = function () { -// return false; -// } - - /// Implements a simple auto-layouted container of methods. /// @param properties /// dir: [string] @@ -364,103 +326,6 @@ WidgetStack.prototype.add = function (widget) { widget.parent = this; return widget; } -// WidgetStack.prototype.relayout = function (layoutFrom) { -// var _TRACE = traceEnter.call(this, "WidgetStack.relayout()"); -// // print("this = " + this); -// // print("this.position = { " + this.position.x + ", " + this.position.y + " }") - -// // this.position.x += 500; - -// // this.setPosition({ -// // x: this.position.x + 5, -// // y: this.position.y -// // }); - -// var layoutStart = { -// 'x': this.position.x + this.border.x, -// 'y': this.position.y + this.border.y -// } - -// var layout = { -// 'pos': Vec2.clone(layoutStart), -// 'bounds': new Rect(this.position.x, this.position.y, this.position.x, this.position.y) -// }; -// // print("this.widgets = " + this.widgets); -// assert(this.widgets, "this.widgets != undefined"); - -// if (this.parent == null) -// this._parentVisible = true; -// var this_isVisible = this.isVisible(); - -// this.widgets.forEach(function (child, i, elems) { -// child.setPosition(layout.pos.x, layout.pos.y); -// child._parentVisible = this_isVisible; -// child.relayout(layout); - -// if (child.isVisible()) { -// layout.bounds.grow({ -// 'x': layout.pos.x + child.getWidth(), -// 'y': layout.pos.y + child.getHeight() -// }); -// layout.pos.x += (child.getWidth() + this.padding.x) * this.layoutDir.x; -// layout.pos.y += (child.getHeight() + this.padding.y) * this.layoutDir.y; -// } -// // elems[i] = child; -// }, this); - -// this._dirty = false; - -// layout.bounds.grow(Vec2.sum(layout.bounds.getBtmRight(), this.border)); -// layout.pos.x += this.border.x * this.layoutDir.x; -// layout.pos.y += this.border.y * this.layoutDir.y; - -// this._calculatedDimensions = { -// 'x': layout.bounds.getWidth(), -// 'y': layout.bounds.getHeight() -// }; -// if (this.backgroundOverlay) { -// this.backgroundOverlay.update({ -// 'x': this.position.x,// - this.border.x, -// 'y': this.position.y,// - this.border.y, -// 'width': this._calculatedDimensions.x, -// 'height': this._calculatedDimensions.y, -// 'visible': this.visible -// }); -// } -// print("RELAYOUT " + this); -// print("layoutDir: " + this.layoutDir.x + ", " + this.layoutDir.y); -// print("padding: " + this.padding.x + ", " + this.padding.y); -// print("topleft: " + layout.bounds.getTopLeft().x + ", " + layout.bounds.getTopLeft().y); -// print("btmright: " + layout.bounds.getBtmRight().x + ", " + layout.bounds.getBtmRight().y); -// print("width, height = " + layout.bounds.getWidth() + ", " + layout.bounds.getHeight()); -// print("start: " + this.position.x + ", " + this.position.y); -// print("end: " + layout.pos.x + ", " + layout.pos.y); - - -// if (layoutFrom) { -// layoutFrom.pos = layout.pos; -// layoutFrom.bounds.grow(layout.getTopLeft()); -// layoutFrom.bounds.grow(layout.getBtmRight()); -// } -// _TRACE.exit(); -// } -// WidgetStack.prototype.getWidth = function () { -// var _TRACE = traceEnter.call(this, "WidgetStack.getWidth()"); -// if (this._dirty || !this._calculatedDimensions) -// this.relayout(); -// _TRACE.exit(); -// return this._calculatedDimensions.x; -// } -// WidgetStack.prototype.getHeight = function () { -// var _TRACE = traceEnter.call(this, "WidgetStack.getHeight()"); -// if (this._dirty || !this._calculatedDimensions) -// this.relayout(); -// _TRACE.exit(); -// return this._calculatedDimensions.y; -// } -// WidgetStack.prototype.show = function () { -// this.relayout(); -// } WidgetStack.prototype.hasOverlay = function (overlayId) { return this.backgroundOverlay && this.backgroundOverlay.getId() === overlayId; } @@ -509,19 +374,6 @@ Icon.prototype.constructor = Icon; Icon.prototype.toString = function () { return "[UI.Icon " + this.id + " ]"; } - -// Icon.prototype.relayout = function (layout) { -// var _TRACE = traceEnter.call(this, "Icon.relayout()"); -// this._dirty = false; -// this.iconOverlay.update({ -// 'x': this.position.x, -// 'y': this.position.y, -// 'width': this.width, -// 'height': this.height, -// 'visible': this.visible -// }); -// _TRACE.exit() -// } Icon.prototype.getHeight = function () { return this.height; } @@ -640,21 +492,6 @@ UI.addAttachment = function (target, rel, update) { UI.updateLayout = function () { // Recalc dimensions - // var touched = {}; - // function recalcDimensions(widget) { - // if (widget.parent && !touched[widget.parent.id]) - // recalcDimensions(widget.parent); - // touched[widget.id] = true; - // widget.cachedWidth = widget.calcWidth(); - // widget.cachedHeight = widget.calcHeight(); - // } - // ui.widgetList.forEach(function (widget) { - // if (!touched[widget.id]) { - // recalcDimensions(widget); - // } - // }); - - // Do this lazily instead ui.widgetList.forEach(function (widget) { widget.clearLayout(); }); @@ -718,7 +555,6 @@ function dispatchEvent(actions, widget, event) { ui.focusedWidget = null; ui.clickedWidget = null; -// ui.selectedWidget = null; var getWidgetWithOverlay = function (overlay) { // print("trying to find overlay: " + overlay); @@ -828,53 +664,3 @@ UI.printWidgets = function () { } })(); - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From f45afe050b0865def9b89acda7af1ce695f7131f Mon Sep 17 00:00:00 2001 From: Seiji Emery Date: Wed, 12 Aug 2015 00:19:11 -0700 Subject: [PATCH 44/53] Added drag actions --- examples/games/solarsystem.js | 80 +++++++++++---- examples/libraries/uiwidgets.js | 167 ++++++++++++++++++++++++++++++-- 2 files changed, 219 insertions(+), 28 deletions(-) diff --git a/examples/games/solarsystem.js b/examples/games/solarsystem.js index 5758ae4fc7..6c701627fb 100644 --- a/examples/games/solarsystem.js +++ b/examples/games/solarsystem.js @@ -650,19 +650,45 @@ var pauseButton = addIcon(systemViewPanel, 'playpause'); var forwardButton = addIcon(systemViewPanel, 'forward'); var zoomPanel = addPanel({ dir: '+y', visible: true }); +zoomPanel.add(new UI.Label({ + text: "Foo", + width: 120, + height: 15, + color: UI.rgb(245, 190, 20), + alpha: 1.0, + backgroundColor: UI.rgb(10, 10, 10), + backgroundAlpha: 0.0 +})); +zoomPanel.add(new UI.Label()); +zoomPanel.add(new UI.Slider({ + width: 120, + height: 15, + backgroundColor: UI.rgb(10, 10, 10), + backgroundAlpha: 0.5, + slider: { + width: 20, + height: 8, + backgroundColor: UI.rgb(120, 120, 120), + backgroundAlpha: 1.0 + } +})); +addIcon(zoomPanel, 'reverse'); UI.updateLayout(); -function hideSubpanels () { - systemViewPanel.setVisible(false); - zoomPanel.setVisible(false); +var subpanels = [ systemViewPanel, zoomPanel ]; +function hideSubpanelsExcept (panel) { + subpanels.forEach(function (x) { + if (x != panel) { + x.setVisible(false); + } + }); } function attachPanel (panel, button) { button.addAction('onClick', function () { - var visible = !panel.isVisible(); - hideSubpanels(); - panel.setVisible(visible); + hideSubpanelsExcept(panel); + panel.setVisible(!panel.isVisible()); UI.updateLayout(); }) @@ -736,19 +762,37 @@ function makeDraggable (panel, target) { if (!target) target = panel; - panel.addAction('onMouseDown', function (event) { - var dragStart = { x: event.x, y: event.y }; - var initialPos = { x: target.position.x, y: target.position.y }; - startDrag({ - updateDrag: function (event) { - target.setPosition( - initialPos.x + event.x - dragStart.x, - initialPos.y + event.y - dragStart.y - ); - UI.updateLayout(); - } - }); + var dragStart = null; + var initialPos = null; + + panel.addAction('onDragBegin', function (event) { + dragStart = { x: event.x, y: event.y }; + initialPos = { x: target.position.x, y: target.position.y }; }); + panel.addAction('onDragUpdate', function (event) { + target.setPosition( + initialPos.x + event.x - dragStart.x, + initialPos.y + event.y - dragStart.y + ); + UI.updateLayout(); + }); + panel.addAction('onDragEnd', function () { + dragStart = dragEnd = null; + }); + + // panel.addAction('onMouseDown', function (event) { + // var dragStart = { x: event.x, y: event.y }; + // var initialPos = { x: target.position.x, y: target.position.y }; + // startDrag({ + // updateDrag: function (event) { + // target.setPosition( + // initialPos.x + event.x - dragStart.x, + // initialPos.y + event.y - dragStart.y + // ); + // UI.updateLayout(); + // } + // }); + // }); } var buttons = icons; diff --git a/examples/libraries/uiwidgets.js b/examples/libraries/uiwidgets.js index 7f1f79645e..9cd9147dee 100644 --- a/examples/libraries/uiwidgets.js +++ b/examples/libraries/uiwidgets.js @@ -349,7 +349,6 @@ WidgetStack.prototype.setColor = function (color) { } var Icon = UI.Icon = function (properties) { - var _TRACE = traceEnter.call(this, "Icon.constructor()"); Widget.prototype.constructor.call(this); this.visible = properties.visible != undefined ? properties.visible : this.visible; @@ -367,7 +366,6 @@ var Icon = UI.Icon = function (properties) { 'visible': this.visible } this.iconOverlay = makeOverlay("image", iconProperties); - _TRACE.exit() } Icon.prototype = new Widget(); Icon.prototype.constructor = Icon; @@ -403,6 +401,143 @@ Icon.prototype.setColor = function (color) { }); } +var Box = UI.Box = function (properties) { + Widget.prototype.constructor.call(this); + + properties = properties || {}; + properties.width = properties.width || 10; + properties.height = properties.height || 10; + properties.visible = properties.visible !== undefined ? properties.visible : this.visible; + properties.x = this.position.x; + properties.y = this.position.y; + + this.width = properties.width; + this.height = properties.height; + this.visible = properties.visible; + + this.overlay = makeOverlay("text", properties); +}; +Box.prototype = new Widget(); +Box.prototype.constructor = Box; +Box.prototype.toString = function () { + return "[UI.Box " + this.id + " ]"; +} +Box.prototype.getWidth = function () { + return this.width; +} +Box.prototype.getHeight = function () { + return this.height; +} +Box.prototype.destroy = function () { + if (this.overlay) { + this.overlay.destroy(); + this.overlay = null; + } +} +Box.prototype.hasOverlay = function (overlayId) { + return this.overlay && this.overlay.id === overlayId; +} +Box.prototype.getOverlay = function () { + return this.overlay; +} +Box.prototype.updateOverlays = function () { + this.overlay.update({ + x: this.position.x, + y: this.position.y, + width: this.width, + height: this.height, + visible: this.isVisible() + }); +} + +var Label = UI.Label = function (properties) { + properties = properties || {}; + properties.text = properties.text || "< bad UI.Label call (text) >"; + + if (!properties.width) { + ui.complain("UI.Label constructor expected width property, not " + properties.width); + this.text = "< bad UI.Label call (width) >"; + properties.width = 220; + } + if (!properties.height) { + ui.complain("UI.Label constructor expected height property, not " + properties.height); + this.text = "< bad UI.Label call (height) >"; + properties.height = 20; + } + properties.color = properties.color || COLOR_WHITE; + + Box.prototype.constructor.call(this, properties); +}; +Label.prototype = new Box(); +Label.prototype.constructor = Label; +Label.prototype.toString = function () { + return "[UI.Label " + this.id + " ]"; +} +Label.prototype.setText = function (text) { + this.text = text; + this.overlay.update({ + text: text + }); +} + +/// Slider element. +/// @param properties: +/// onValueChanged +var Slider = UI.Slider = function (properties) { + Box.prototype.constructor.call(this, properties); + // print("CONSTRUCTING SLIDER") + this.value = properties.value || 0.0; + this.maxValue = properties.maxValue || 1.0; + this.minValue = properties.minValue || 0.0; + + this.slider = new Box(properties.slider); + this.slider.parent = this; + + (function() { + // var x0; + this.slider.addAction('onDragBegin', function (event) { + // x0 = event.x; + }); + this.slider.addAction('onDragUpdate', function (event, widget) { + var rx = Math.max(event.x - widget.position.x, 0.0); + var v = Math.min(rx, widget.width) / widget.width; + widget.value = widget.minValue + (widget.maxValue - widget.minValue) * v; + UI.updateLayout(); + }); + this.slider.addAction('onDragEnd', function () {}); + }).apply(this); +}; +Slider.prototype = new Box(); +Slider.prototype.constructor = Slider; + +Slider.prototype.toString = function () { + return "[UI.Slider " + this.id + " ]"; +} + +Slider.prototype.applyLayout = function () { + if (!this.slider) { + ui.complain("Slider.applyLayout on " + this + " failed"); + return; + } + var val = (this.value - this.minValue) / (this.maxValue - this.minValue); + this.slider.position.x = this.position.x + this.width * val; + this.slider.position.y = this.position.y + (this.height - this.slider.height) * 0.5; +} + + + + + +var Checkbox = UI.Checkbox = function (properties) { + +}; + + + + + + + // New layout functions Widget.prototype.applyLayout = function () {}; Widget.prototype.updateOverlays = function () {}; @@ -555,6 +690,7 @@ function dispatchEvent(actions, widget, event) { ui.focusedWidget = null; ui.clickedWidget = null; +ui.draggedWidget = null; var getWidgetWithOverlay = function (overlay) { // print("trying to find overlay: " + overlay); @@ -604,7 +740,12 @@ UI.handleMouseMove = function (event) { print("got focus: " + focused); - if (focused != ui.focusedWidget) { + if (ui.clickedWidget && !ui.draggedWidget && ui.clickedWidget.actions['onDragBegin']) { + ui.draggedWidget = ui.clickedWidget; + dispatchEvent('onDragBegin', event, ui.draggedWidget); + } else if (ui.draggedWidget) { + dispatchEvent('onDragUpdate', event, ui.draggedWidget); + } else if (focused != ui.focusedWidget) { if (focused) dispatchEvent('onMouseOver', event, focused); if (ui.focusedWidget) @@ -616,23 +757,29 @@ UI.handleMouseMove = function (event) { UI.handleMousePress = function (event) { print("Mouse clicked"); UI.handleMouseMove(event); + ui.clickedWidget = ui.focusedWidget; if (ui.focusedWidget) { - ui.clickedWidget = ui.focusedWidget; dispatchEvent('onMouseDown', event, ui.focusedWidget); } } UI.handleMouseRelease = function (event) { print("Mouse released"); - UI.handleMouseMove(event); - if (ui.focusedWidget) { - dispatchEvent('onMouseUp', event, ui.focusedWidget); - if (ui.clickedWidget == ui.focusedWidget) { - dispatchEvent('onClick', event, ui.focusedWidget); + if (ui.draggedWidget) { + dispatchEvent('onDragEnd', event, ui.draggedWidget); + } else { + UI.handleMouseMove(event); + if (ui.focusedWidget) { + dispatchEvent('onMouseUp', event, ui.focusedWidget); + + if (ui.clickedWidget == ui.focusedWidget) { + dispatchEvent('onClick', event, ui.focusedWidget); + } } - ui.clickedWidget = null;; } + ui.clickedWidget = null; + ui.draggedWidget = null; } UI.teardown = function () { From 293f146cfc1b78975dacfbc9fe2f95e05144e5d2 Mon Sep 17 00:00:00 2001 From: Seiji Emery Date: Wed, 12 Aug 2015 10:44:46 -0700 Subject: [PATCH 45/53] added debug ui, fixed non-targetable ui boxes/labels --- examples/games/solarsystem.js | 209 +++++++++++++++++++++++++------- examples/libraries/uiwidgets.js | 175 ++++++++++++++++++++------ 2 files changed, 306 insertions(+), 78 deletions(-) diff --git a/examples/games/solarsystem.js b/examples/games/solarsystem.js index 6c701627fb..c4c3f95f2a 100644 --- a/examples/games/solarsystem.js +++ b/examples/games/solarsystem.js @@ -629,10 +629,135 @@ function addPanel (properties) { return panel; } +function makeDraggable (panel, target) { + if (!target) + target = panel; + + var dragStart = null; + var initialPos = null; + + panel.addAction('onDragBegin', function (event) { + dragStart = { x: event.x, y: event.y }; + initialPos = { x: target.position.x, y: target.position.y }; + }); + panel.addAction('onDragUpdate', function (event) { + target.setPosition( + initialPos.x + event.x - dragStart.x, + initialPos.y + event.y - dragStart.y + ); + UI.updateLayout(); + }); + panel.addAction('onDragEnd', function () { + dragStart = dragEnd = null; + }); +} + // var panelContainer = new UI.WidgetContainer(); // panelContainer.setPosition(500, 250); // panelContainer.setVisible(true); +var demoPane = addPanel({ dir: '+y' }); +var demoLabel = demoPane.add(new UI.Label({ + text: "< no events >", + width: 400, height: 20 +})); +var demoButton = demoPane.add(new UI.Box({ + width: 200, height: 80, + text: "Button" +})); +function setText(text) { + return function () { + demoLabel.setText(text); + UI.updateLayout(); + }; +} +function addDebugActions(widget, msg, actions) { + actions.forEach(function(action) { + widget.addAction(action, setText(action + " " + msg + widget)); + }); +} + +var debugEvents = [ + 'onMouseOver', + 'onMouseExit', + 'onMouseDown', + 'onMouseUp', + 'onDragBegin', + 'onDragEnd', + 'onDragUpdate' +]; +addDebugActions(demoPane, "(container) ", debugEvents); +addDebugActions(demoButton, "(button) ", debugEvents); +addDebugActions(demoLabel, "(label) ", debugEvents); + +// demoPane.addAction('onMouseOver', setText("onMouseOver " + demoPane)); +// demoPane.addAction('onMouseExit', setText("onMouseExit " + demoPane)); +// demoPane.addAction('onMouseDown', setText("onMouseDown " + demoPane)); +// demoPane.addAction('onMouseUp', setText("onMouseUp " + demoPane)); +makeDraggable(demoPane, demoPane); +demoPane.setPosition(600, 200); + +// demoButton.addAction('onMouseOver', setText("onMouseOver " + demoButton)); +// demoButton.addAction('onMouseExit', setText("onMouseExit " + demoButton)); +// demoButton.addAction() + + +var resizablePanel = new UI.Label({ + text: "Resizable panel", + width: 200, height: 200, + backgroundAlpha: 0.5 +}); +resizablePanel.setPosition(1100, 200); + + +debugEvents.forEach(function (action) { + resizablePanel.addAction(action, function (event, widget) { + widget.setText(action + " " + widget); + }); +}) + +addDebugActions(resizablePanel, "", debugEvents); + +function join(obj) { + var s = "{"; + var sep = "\n"; + for (var k in obj) { + s += sep + k + ": " + (""+obj[k]).replace("\n", "\n"); + sep = ",\n"; + } + if (s.length > 1) + return s + " }"; + return s + "}"; +} + +// resizablePanel.getOverlay().update({ +// text: "" + join(resizablePanel.actions) +// }); + + +setText = addDebugActions = undefined; + + +var tooltipWidget = new UI.Label({ + text: "", + width: 500, height: 20, + visible: false +}); + +function addTooltip (widget, text) { + widget.addAction('onMouseOver', function (event, widget) { + tooltipWidget.setVisible(true); + tooltipWidget.setPosition(widget.position.x + widget.getWidth() + 20, widget.position.y); + tooltipWidget.setText(text); + UI.updateLayout(); + }); + widget.addAction('onMouseExit', function () { + tooltipWidget.setVisible(false); + UI.updateLayout(); + }); +} +UI.showWidgetList(); + var mainPanel = addPanel({ dir: '+y' }); mainPanel.setPosition(500, 250); mainPanel.setVisible(true); @@ -644,30 +769,56 @@ var settingsButton = addIcon(mainPanel, 'settings'); var stopButton = addIcon(mainPanel, 'close'); +addTooltip(systemViewButton, "system view"); +addTooltip(zoomButton, "zoom"); +addTooltip(satelliteButton, "satelite view"); +addTooltip(settingsButton, "settings"); +addTooltip(stopButton, "exit"); + + var systemViewPanel = addPanel({ dir: '+x', visible: false }); var reverseButton = addIcon(systemViewPanel, 'reverse'); var pauseButton = addIcon(systemViewPanel, 'playpause'); var forwardButton = addIcon(systemViewPanel, 'forward'); var zoomPanel = addPanel({ dir: '+y', visible: true }); -zoomPanel.add(new UI.Label({ +var label = new UI.Label({ text: "Foo", width: 120, height: 15, - color: UI.rgb(245, 190, 20), + color: UI.rgb(245, 290, 20), alpha: 1.0, backgroundColor: UI.rgb(10, 10, 10), backgroundAlpha: 0.0 -})); +}); +zoomPanel.add(label); +label.addAction('onMouseOver', function () { + label.setText("Bar"); + UI.updateLayout(); +}); +label.addAction('onMouseExit', function () { + label.setText("Foo"); + UI.updateLayout(); +}); +label.setText("Label id: " + label.id + ", parent id " + label.parent.id); +label.parent.addAction('onMouseOver', function () { + label.setText("on parent"); + UI.updateLayout(); +}); +label.parent.addAction('onMouseExit', function () { + label.setText('exited parent'); + UI.updateLayout(); +}); + zoomPanel.add(new UI.Label()); zoomPanel.add(new UI.Slider({ width: 120, - height: 15, + height: 25, backgroundColor: UI.rgb(10, 10, 10), backgroundAlpha: 0.5, slider: { - width: 20, - height: 8, + width: 30, + height: 15, backgroundColor: UI.rgb(120, 120, 120), backgroundAlpha: 1.0 } @@ -675,6 +826,8 @@ zoomPanel.add(new UI.Slider({ addIcon(zoomPanel, 'reverse'); UI.updateLayout(); +UI.showWidgetList(); + var subpanels = [ systemViewPanel, zoomPanel ]; function hideSubpanelsExcept (panel) { @@ -714,10 +867,13 @@ var addColorToggle = function (widget) { reverseButton.addAction('onClick', function() { }); -pauseButton.addAction('onClick', function() { +systemViewPanel.addAction('onMouseOver', function() { + hideSubpanels(); + UI.updateLayout(); // paused ? resume() : pause(); }); + zoomButton.addAction('onClick', function() { hideSubpanels(); UI.updateLayout(); @@ -731,7 +887,7 @@ zoomButton.addAction('onClick', function() { } }); UI.updateLayout(); - +UI.showWidgetList(); stopButton.addAction('onClick', function() { // Script.stop(); @@ -758,42 +914,7 @@ stopButton.addAction('onClick', function() { } })(); -function makeDraggable (panel, target) { - if (!target) - target = panel; - var dragStart = null; - var initialPos = null; - - panel.addAction('onDragBegin', function (event) { - dragStart = { x: event.x, y: event.y }; - initialPos = { x: target.position.x, y: target.position.y }; - }); - panel.addAction('onDragUpdate', function (event) { - target.setPosition( - initialPos.x + event.x - dragStart.x, - initialPos.y + event.y - dragStart.y - ); - UI.updateLayout(); - }); - panel.addAction('onDragEnd', function () { - dragStart = dragEnd = null; - }); - - // panel.addAction('onMouseDown', function (event) { - // var dragStart = { x: event.x, y: event.y }; - // var initialPos = { x: target.position.x, y: target.position.y }; - // startDrag({ - // updateDrag: function (event) { - // target.setPosition( - // initialPos.x + event.x - dragStart.x, - // initialPos.y + event.y - dragStart.y - // ); - // UI.updateLayout(); - // } - // }); - // }); -} var buttons = icons; @@ -968,7 +1089,7 @@ panels.map(function (panel) { makeDraggable(panel, mainPanel); }); // } // } -UI.printWidgets(); +// UI.printWidgets(); // Clean up models, UI panels, lines, and button overlays function teardown() { diff --git a/examples/libraries/uiwidgets.js b/examples/libraries/uiwidgets.js index 9cd9147dee..b73788d4eb 100644 --- a/examples/libraries/uiwidgets.js +++ b/examples/libraries/uiwidgets.js @@ -120,12 +120,12 @@ var rgb = UI.rgb = function (r, g, b) { ui.err("Invalid args to UI.rgb (" + r + ", " + g + ", " + b + ")"); return null; } - return { 'r': r, 'g': g, 'b': b }; + return { 'red': r, 'green': g, 'blue': b }; } var rgba = UI.rgba = function (r, g, b, a) { if (typeof(r) == 'string') return rgb(r); - return { 'r': r || 0, 'g': g || 0, 'b': b || 0, 'a': a }; + return { 'red': r || 0, 'green': g || 0, 'blue': b || 0, 'a': a }; } /// Protected UI state @@ -254,6 +254,8 @@ Widget.prototype.getHeight = function () { Widget.prototype.hasOverlay = function () { return false; } +Widget.prototype.applyLayout = function () {}; +Widget.prototype.updateOverlays = function () {}; /// Implements a simple auto-layouted container of methods. /// @param properties @@ -435,7 +437,7 @@ Box.prototype.destroy = function () { } } Box.prototype.hasOverlay = function (overlayId) { - return this.overlay && this.overlay.id === overlayId; + return this.overlay && this.overlay.getId() === overlayId; } Box.prototype.getOverlay = function () { return this.overlay; @@ -465,6 +467,8 @@ var Label = UI.Label = function (properties) { properties.height = 20; } properties.color = properties.color || COLOR_WHITE; + properties.alpha = properties.alpha || properties.color.a || 1.0; + properties.backgroundAlpha = properties.backgroundAlpha || 0.0; Box.prototype.constructor.call(this, properties); }; @@ -493,19 +497,43 @@ var Slider = UI.Slider = function (properties) { this.slider = new Box(properties.slider); this.slider.parent = this; - (function() { - // var x0; - this.slider.addAction('onDragBegin', function (event) { - // x0 = event.x; - }); - this.slider.addAction('onDragUpdate', function (event, widget) { - var rx = Math.max(event.x - widget.position.x, 0.0); - var v = Math.min(rx, widget.width) / widget.width; - widget.value = widget.minValue + (widget.maxValue - widget.minValue) * v; - UI.updateLayout(); - }); - this.slider.addAction('onDragEnd', function () {}); - }).apply(this); + this.statusLabel = new Label({ + text: "< UI.Slider Status log > ", + width: 300, + height: 30, + color: UI.rgb(255, 255, 255), + alpha: 0.9 + }); + // this.statusLabel.parent = this; + + // var x0; + var i = 0; + this.addAction('onDragBegin', function (event) { + statusLabel.setText("Drag begin"); + UI.updateLayout(); + i = 0; + // x0 = event.x; + }); + this.addAction('onDragUpdate', function (event, widget) { + statusLabel.setText("Drag Update " + i++); + var rx = Math.max(event.x - widget.position.x, 0.0); + var v = Math.min(rx, widget.width) / widget.width; + widget.value = widget.minValue + (widget.maxValue - widget.minValue) * v; + UI.updateLayout(); + }); + this.addAction('onDragEnd', function () { + statusLabel.setText("Drag end"); + UI.updateLayout(); + }); + this.addAction('onMouseOver', function () { + print("Slider MOUSEOVER"); + statusLabel.setText("mouseover"); + UI.updateLayout(); + }); + this.addAction('onMouseExit', function () { + statusLabel.setText("mousexit"); + UI.updateLayout(); + }); }; Slider.prototype = new Box(); Slider.prototype.constructor = Slider; @@ -524,10 +552,6 @@ Slider.prototype.applyLayout = function () { this.slider.position.y = this.position.y + (this.height - this.slider.height) * 0.5; } - - - - var Checkbox = UI.Checkbox = function (properties) { }; @@ -539,8 +563,7 @@ var Checkbox = UI.Checkbox = function (properties) { // New layout functions -Widget.prototype.applyLayout = function () {}; -Widget.prototype.updateOverlays = function () {}; + Icon.prototype.getWidth = function () { @@ -593,13 +616,11 @@ WidgetStack.prototype.getHeight = function () { return this.dimensions.y; } WidgetStack.prototype.applyLayout = function () { - print("Applying layout " + this); var x = this.position.x + this.border.x; var y = this.position.y + this.border.y; this.widgets.forEach(function (widget) { widget.setPosition(x, y); - print("setting position for " + widget + ": " + x + ", " + y) x += (widget.getWidth() + this.padding.x) * this.layoutDir.x; y += (widget.getHeight() + this.padding.y) * this.layoutDir.y; widget._parentVisible = this.isVisible(); @@ -688,23 +709,109 @@ function dispatchEvent(actions, widget, event) { _TRACE.exit(); } +// Focus state ui.focusedWidget = null; ui.clickedWidget = null; ui.draggedWidget = null; -var getWidgetWithOverlay = function (overlay) { - // print("trying to find overlay: " + overlay); +// Debugging ui +var statusPos = new Vec2(15, 20); +var statusDim = new Vec2(500, 20); +function makeStatusWidget(defaultText, alpha) { + var label = new Box({ + text: defaultText, + width: statusDim.x, + height: statusDim.y, + color: COLOR_WHITE, + alpha: alpha || 0.98, + backgroundAlpha: 0.0 + }); + label.updateText = function (text) { + label.getOverlay().update({ + text: text + }); + } + label.setPosition(statusPos.x, statusPos.y); + statusPos.y += statusDim.y; + return label; +} +ui.focusStatus = makeStatusWidget(""); +ui.eventStatus = makeStatusWidget("", 0.85); +(function () { + var eventList = []; + var maxEvents = 8; + ui.logEvent = function (event) { + eventList.push(event); + if (eventList.length >= maxEvents) + eventList.shift(); + ui.eventStatus.updateText(eventList.join('\n')); + } +})(); + +// Add debug list of all widgets + attachments +(function () { + var widgetListHeader = makeStatusWidget("Widgets: ", 0.98); + var widgetList = makeStatusWidget("", 0.85); + var attachmentListHeader = makeStatusWidget("Attachments: ", 0.98); + var attachmentList = makeStatusWidget("", 0.85); + var lineHeight = 20; + + // widgetListHeader.getOverlay().update({ + // // color: { red: 0.1, green: 0.5, blue: 0.9 }, + // backgroundColor: { red: 120, green: 250, blue: 12 }, + // backgroundAlpha: 0.9 + // }); + + function relayout (x, y) { + widgetListHeader.setPosition(x, y); y += lineHeight; + widgetList.updateText(ui.widgetList.map(function (widget) { + return "" + widget + " actions: " + (Object.keys(widget.actions).join(", ") || "none"); + }).join("\n") || "no widgets"); + widgetList.setPosition(x, y); + y += lineHeight * (ui.widgetList.length || 1); + + attachmentListHeader.setPosition(x, y); y += lineHeight; + attachmentList.updateText(ui.attachmentList.map(function (attachment) { + return "[attachment target: " + attachment.target + ", to: " + attachment.rel + "]"; + }).join('\n') || "no attachments"); + attachmentList.setPosition(x, y); + y += lineHeight * (ui.widgetList.length || 1); + + UI.updateLayout(); + } + + var defaultX = 500; + var defaultY = 20; + UI.showWidgetList = function (x, y) { + widgetListHeader.setVisible(true); + widgetList.setVisible(true); + attachmentListHeader.setVisible(true); + attachmentList.setVisible(true); + relayout(x || defaultX, y || defaultY); + } + UI.hideWidgetList = function () { + widgetListHeader.setVisible(false); + widgetList.setVisible(false); + attachmentListHeader.setVisible(false); + attachmentList.setVisible(true); + } +})(); + +UI.showWidgetList(); + +// Tries to find a widget with an overlay matching overlay. +// Used by getFocusedWidget(), dispatchEvent(), etc +var getWidgetWithOverlay = function (overlay) { var foundWidget = null; ui.widgetList.forEach(function(widget) { if (widget.hasOverlay(overlay)) { - // print("found overlay in " + widget); foundWidget = widget; return; } }); - // if (!foundWidget) - // print("could not find overlay"); + + ui.focusStatus.updateText("Widget focus: " + foundWidget); return foundWidget; } @@ -720,17 +827,17 @@ var dispatchEvent = function (action, event, widget) { } if (widget.actions[action]) { - print("Dispatching action '" + action + "'' to " + widget); + ui.logEvent("Dispatching action '" + action + "'' to " + widget) dispatchActions(widget.actions[action]); } else { for (var parent = widget.parent; parent != null; parent = parent.parent) { if (parent.actions[action]) { - print("Dispatching action '" + action + "'' to parent widget " + widget); + ui.logEvent("Dispatching action '" + action + "'' to parent widget " + widget); dispatchActions(parent.actions[action]); return; } } - print("No action '" + action + "' in " + widget); + ui.logEvent("No action '" + action + "' in " + widget); } } @@ -746,10 +853,10 @@ UI.handleMouseMove = function (event) { } else if (ui.draggedWidget) { dispatchEvent('onDragUpdate', event, ui.draggedWidget); } else if (focused != ui.focusedWidget) { - if (focused) - dispatchEvent('onMouseOver', event, focused); if (ui.focusedWidget) dispatchEvent('onMouseExit', event, ui.focusedWidget); + if (focused) + dispatchEvent('onMouseOver', event, focused); ui.focusedWidget = focused; } } From 9fe1aaf1826479f158d45639665c8c2d8c00cb41 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 12 Aug 2015 15:21:06 -0700 Subject: [PATCH 46/53] Disable saving / loading eye tracker calibrations so can use old SDK --- interface/src/devices/EyeTracker.cpp | 47 +++++++++++++++------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/interface/src/devices/EyeTracker.cpp b/interface/src/devices/EyeTracker.cpp index bea04c16a2..8487ea02bc 100644 --- a/interface/src/devices/EyeTracker.cpp +++ b/interface/src/devices/EyeTracker.cpp @@ -151,20 +151,21 @@ void EyeTracker::onStreamStarted() { } } - if (_isStreaming) { - // Automatically load calibration if one has been saved. - QString availableCalibrations = QString(smi_getAvailableCalibrations()); - if (availableCalibrations.contains(HIGH_FIDELITY_EYE_TRACKER_CALIBRATION)) { - result = smi_loadCalibration(HIGH_FIDELITY_EYE_TRACKER_CALIBRATION); - if (result != SMI_RET_SUCCESS) { - qCWarning(interfaceapp) << "Eye Tracker: Error loading calibration:" << smiReturnValueToString(result); - QMessageBox::warning(nullptr, "Eye Tracker Error", "Error loading calibration" - + smiReturnValueToString(result)); - } else { - qCDebug(interfaceapp) << "Eye Tracker: Loaded calibration"; - } - } - } + // TODO: Re-enable once saving / loading calibrations is working + //if (_isStreaming) { + // // Automatically load calibration if one has been saved. + // QString availableCalibrations = QString(smi_getAvailableCalibrations()); + // if (availableCalibrations.contains(HIGH_FIDELITY_EYE_TRACKER_CALIBRATION)) { + // result = smi_loadCalibration(HIGH_FIDELITY_EYE_TRACKER_CALIBRATION); + // if (result != SMI_RET_SUCCESS) { + // qCWarning(interfaceapp) << "Eye Tracker: Error loading calibration:" << smiReturnValueToString(result); + // QMessageBox::warning(nullptr, "Eye Tracker Error", "Error loading calibration" + // + smiReturnValueToString(result)); + // } else { + // qCDebug(interfaceapp) << "Eye Tracker: Loaded calibration"; + // } + // } + //} } #endif @@ -257,10 +258,11 @@ void EyeTracker::calibrate(int points) { if (result != SMI_RET_SUCCESS) { qCWarning(interfaceapp) << "Eye Tracker: Error performing calibration:" << smiReturnValueToString(result); } else { - result = smi_saveCalibration(HIGH_FIDELITY_EYE_TRACKER_CALIBRATION); - if (result != SMI_RET_SUCCESS) { - qCWarning(interfaceapp) << "Eye Tracker: Error saving calibration:" << smiReturnValueToString(result); - } + // TODO: Re - enable once saving / loading calibrations is working + //result = smi_saveCalibration(HIGH_FIDELITY_EYE_TRACKER_CALIBRATION); + //if (result != SMI_RET_SUCCESS) { + // qCWarning(interfaceapp) << "Eye Tracker: Error saving calibration:" << smiReturnValueToString(result); + //} } } @@ -288,10 +290,11 @@ QString EyeTracker::smiReturnValueToString(int value) { return "Eye cameras not available"; case smi_ErrorReturnValue::SMI_ERROR_OCULUS_RUNTIME_NOT_SUPPORTED: return "Oculus runtime not supported"; - case smi_ErrorReturnValue::SMI_ERROR_FILE_NOT_FOUND: - return "File not found"; - case smi_ErrorReturnValue::SMI_ERROR_FILE_EMPTY: - return "File empty"; + // TODO: Re-enable once saving / loading calibrations is working + //case smi_ErrorReturnValue::SMI_ERROR_FILE_NOT_FOUND: + // return "File not found"; + //case smi_ErrorReturnValue::SMI_ERROR_FILE_EMPTY: + // return "File empty"; case smi_ErrorReturnValue::SMI_ERROR_UNKNOWN: return "Unknown error"; default: From 7298fa7e2c5f23ebf15ebef5dc65f738bc79d020 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 12 Aug 2015 15:22:24 -0700 Subject: [PATCH 47/53] Log successful start of eye tracker streaming --- interface/src/devices/EyeTracker.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/interface/src/devices/EyeTracker.cpp b/interface/src/devices/EyeTracker.cpp index 8487ea02bc..532c1d41f3 100644 --- a/interface/src/devices/EyeTracker.cpp +++ b/interface/src/devices/EyeTracker.cpp @@ -149,6 +149,8 @@ void EyeTracker::onStreamStarted() { if (result != SMI_ERROR_HMD_NOT_SUPPORTED) { QMessageBox::warning(nullptr, "Eye Tracker Error", smiReturnValueToString(result)); } + } else { + qCDebug(interfaceapp) << "Eye Tracker: Started streaming"; } // TODO: Re-enable once saving / loading calibrations is working From 6a0a08eafb3bd45758a7adcdd82d51bdfc23dda5 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 12 Aug 2015 15:25:34 -0700 Subject: [PATCH 48/53] Consolidate identical code paths --- interface/src/avatar/MyAvatar.cpp | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 6bd65701d3..f51a077304 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -932,14 +932,8 @@ void MyAvatar::updateLookAtTargetAvatar() { const float HUMAN_EYE_SEPARATION = 0.065f; float myEyeSeparation = glm::length(getHead()->getLeftEyePosition() - getHead()->getRightEyePosition()); gazeOffset = gazeOffset * HUMAN_EYE_SEPARATION / myEyeSeparation; - - if (Application::getInstance()->isHMDMode()) { - avatar->getHead()->setCorrectedLookAtPosition(Application::getInstance()->getViewFrustum()->getPosition() - + gazeOffset); - } else { - avatar->getHead()->setCorrectedLookAtPosition(Application::getInstance()->getViewFrustum()->getPosition() - + gazeOffset); - } + avatar->getHead()->setCorrectedLookAtPosition(Application::getInstance()->getViewFrustum()->getPosition() + + gazeOffset); } else { avatar->getHead()->clearCorrectedLookAtPosition(); } From 10ca3f7a8faa1a321c251d9f119c54812df1c395 Mon Sep 17 00:00:00 2001 From: Seiji Emery Date: Wed, 12 Aug 2015 15:39:34 -0700 Subject: [PATCH 49/53] fixes --- examples/games/solarsystem.js | 83 ++++++-- examples/libraries/uiwidgets.js | 328 +++++++++++++++++++++----------- 2 files changed, 288 insertions(+), 123 deletions(-) diff --git a/examples/games/solarsystem.js b/examples/games/solarsystem.js index c4c3f95f2a..65029c174d 100644 --- a/examples/games/solarsystem.js +++ b/examples/games/solarsystem.js @@ -701,7 +701,6 @@ demoPane.setPosition(600, 200); // demoButton.addAction('onMouseExit', setText("onMouseExit " + demoButton)); // demoButton.addAction() - var resizablePanel = new UI.Label({ text: "Resizable panel", width: 200, height: 200, @@ -709,6 +708,17 @@ var resizablePanel = new UI.Label({ }); resizablePanel.setPosition(1100, 200); +var debugToggle = new UI.Box({ + text: "debug", width: 150, height: 20 +}); +debugToggle.setPosition(1000, 0); +debugToggle.addAction('onClick', function () { + UI.debug.setVisible(!UI.debug.isVisible()); +}) + + + + debugEvents.forEach(function (action) { resizablePanel.addAction(action, function (event, widget) { @@ -743,7 +753,6 @@ var tooltipWidget = new UI.Label({ width: 500, height: 20, visible: false }); - function addTooltip (widget, text) { widget.addAction('onMouseOver', function (event, widget) { tooltipWidget.setVisible(true); @@ -756,7 +765,6 @@ function addTooltip (widget, text) { UI.updateLayout(); }); } -UI.showWidgetList(); var mainPanel = addPanel({ dir: '+y' }); mainPanel.setPosition(500, 250); @@ -810,23 +818,75 @@ label.parent.addAction('onMouseExit', function () { UI.updateLayout(); }); -zoomPanel.add(new UI.Label()); -zoomPanel.add(new UI.Slider({ - width: 120, - height: 25, +var sliderLayout = zoomPanel.add(new UI.WidgetStack({ + dir: '+x', visible: true, backgroundAlpha: 0.0 +})); +var sliderLabel = sliderLayout.add(new UI.Label({ + text: " ", width: 45, height: 20 +})); +var slider = sliderLayout.add(new UI.Slider({ + value: 10, maxValue: 100, minValue: 0, + width: 300, height: 20, backgroundColor: UI.rgb(10, 10, 10), - backgroundAlpha: 0.5, - slider: { + backgroundAlpha: 1.0, + slider: { // slider knob width: 30, - height: 15, + height: 18, backgroundColor: UI.rgb(120, 120, 120), backgroundAlpha: 1.0 } })); +sliderLabel.setText("" + (+slider.getValue().toFixed(1))); +slider.onValueChanged = function (value) { + sliderLabel.setText("" + (+value.toFixed(1))); + UI.updateLayout(); +} + + + + +var checkBoxLayout = zoomPanel.add(new UI.WidgetStack({ + dir: '+x', visible: true, backgroundAlpha: 0.0 +})); +// var padding = checkBoxLayout.add(new UI.Label({ +// text: " ", width: 45, height: 20 +// })); +var checkBoxLabel = checkBoxLayout.add(new UI.Label({ + text: "set red", width: 60, height: 20, + backgroundAlpha: 0.0 +})); +checkBoxLabel.setText("set red"); + +var defaultColor = UI.rgb(10, 10, 10); +var redColor = UI.rgb(210, 80, 80); + +var checkbox = checkBoxLayout.add(new UI.Checkbox({ + width: 20, height: 20, padding: { x: 3, y: 3 }, + backgroundColor: defaultColor, + backgroundAlpha: 0.9, + checked: false, + onValueChanged: function (red) { + zoomPanel.getOverlay().update({ + // backgroundAlpha: 0.1, + backgroundColor: red ? redColor : defaultColor + }); +} +})); + +// checkbox.onValueChanged = function (red) { +// zoomPanel.getOverlay().update({ +// // backgroundAlpha: 0.1, +// backgroundColor: red ? redColor : defaultColor +// }); +// } + + + + + addIcon(zoomPanel, 'reverse'); UI.updateLayout(); -UI.showWidgetList(); var subpanels = [ systemViewPanel, zoomPanel ]; @@ -887,7 +947,6 @@ zoomButton.addAction('onClick', function() { } }); UI.updateLayout(); -UI.showWidgetList(); stopButton.addAction('onClick', function() { // Script.stop(); diff --git a/examples/libraries/uiwidgets.js b/examples/libraries/uiwidgets.js index b73788d4eb..e0801e3a81 100644 --- a/examples/libraries/uiwidgets.js +++ b/examples/libraries/uiwidgets.js @@ -454,7 +454,8 @@ Box.prototype.updateOverlays = function () { var Label = UI.Label = function (properties) { properties = properties || {}; - properties.text = properties.text || "< bad UI.Label call (text) >"; + if (properties.text === undefined || properties.text === null) + properties.text = "< bad UI.Label call (text) >"; if (!properties.width) { ui.complain("UI.Label constructor expected width property, not " + properties.width); @@ -493,47 +494,54 @@ var Slider = UI.Slider = function (properties) { this.value = properties.value || 0.0; this.maxValue = properties.maxValue || 1.0; this.minValue = properties.minValue || 0.0; + this.padding = properties.padding || { + x: 4, y: 4 + } + this.onValueChanged = properties.onValueChanged || function () {}; this.slider = new Box(properties.slider); this.slider.parent = this; - this.statusLabel = new Label({ - text: "< UI.Slider Status log > ", - width: 300, - height: 30, - color: UI.rgb(255, 255, 255), - alpha: 0.9 - }); - // this.statusLabel.parent = this; + var updateSliderPos = function (event, widget) { + var rx = Math.max(event.x * 1.0 - widget.position.x - widget.slider.width * 0.5, 0.0); + var width = Math.max(widget.width - widget.slider.width - widget.padding.x * 2.0, 0.0); + var v = Math.min(rx, width) / (width || 1); - // var x0; - var i = 0; - this.addAction('onDragBegin', function (event) { - statusLabel.setText("Drag begin"); + widget.value = widget.minValue + ( + widget.maxValue - widget.minValue) * v; + widget.onValueChanged(widget.value); UI.updateLayout(); - i = 0; - // x0 = event.x; + } + + var widget = this; + this.addAction('onMouseDown', function (event) { + sliderRel.x = sliderRel.y = 0.0; + // sliderRel.x = widget.slider.width * 0.5; + // sliderRel.y = widget.slider.height * 0.5; + updateSliderPos(event, widget); + + // hack + ui.clickedWidget = ui.draggedWidget = widget.slider; }); - this.addAction('onDragUpdate', function (event, widget) { - statusLabel.setText("Drag Update " + i++); - var rx = Math.max(event.x - widget.position.x, 0.0); - var v = Math.min(rx, widget.width) / widget.width; - widget.value = widget.minValue + (widget.maxValue - widget.minValue) * v; - UI.updateLayout(); - }); - this.addAction('onDragEnd', function () { - statusLabel.setText("Drag end"); - UI.updateLayout(); - }); - this.addAction('onMouseOver', function () { - print("Slider MOUSEOVER"); - statusLabel.setText("mouseover"); - UI.updateLayout(); - }); - this.addAction('onMouseExit', function () { - statusLabel.setText("mousexit"); - UI.updateLayout(); + + var sliderRel = {}; + this.slider.addAction('onMouseDown', function (event) { + sliderRel.x = widget.slider.position.x - event.x; + sliderRel.y = widget.slider.position.y - event.y; + event.x += sliderRel.x; + event.y += sliderRel.y; + updateSliderPos(event, widget); }); + this.slider.addAction('onDragBegin', function (event) { + event.x += sliderRel.x; + event.y += sliderRel.y; + updateSliderPos(event, widget); + }) + this.slider.addAction('onDragUpdate', function (event) { + event.x += sliderRel.x; + event.y += sliderRel.y; + updateSliderPos(event, widget); + }) }; Slider.prototype = new Box(); Slider.prototype.constructor = Slider; @@ -541,29 +549,79 @@ Slider.prototype.constructor = Slider; Slider.prototype.toString = function () { return "[UI.Slider " + this.id + " ]"; } - Slider.prototype.applyLayout = function () { if (!this.slider) { ui.complain("Slider.applyLayout on " + this + " failed"); return; } var val = (this.value - this.minValue) / (this.maxValue - this.minValue); - this.slider.position.x = this.position.x + this.width * val; - this.slider.position.y = this.position.y + (this.height - this.slider.height) * 0.5; + this.slider.position.x = this.position.x + this.padding.x + (this.width - this.slider.width - this.padding.x * 2.0) * val; + this.slider.position.y = this.position.y + /*this.padding.y +*/ (this.height - this.slider.height) * 0.5; +} +Slider.prototype.getValue = function () { + return this.value; +} +Slider.prototype.setValue = function (value) { + this.value = value; + this.onValueChanged(value); + UI.updateLayout(); } + var Checkbox = UI.Checkbox = function (properties) { + Box.prototype.constructor.call(this, properties); + this.checked = properties.checked !== undefined ? properties.checked : true; + this.padding = properties.padding || { x: 4, y: 4 }; + + properties.checkMark = properties.checkMark || {}; + + // Keep square + var r = Math.min(this.width - this.padding.x * 2.0, this.height - this.padding.y * 2.0); + properties.checkMark.width = properties.checkMark.height = r; + properties.checkMark.visible = false; + properties.checkMark.backgroundColor = properties.checkMark.backgroundColor || + properties.checkMark.color || rgb(77, 185, 77); + properties.checkMark.backgroundAlpha = properties.checkMark.backgroundAlpha || + properties.checkMark.alpha || 1.0; + this.checkMark = new Box(properties.checkMark); + this.checkMark.setVisible(this.checked); + this.checkMark.setPosition( + this.position.x + (this.width - this.checkMark.width) * 0.5, + this.position.y + (this.height - this.checkMark.height) * 0.5 + ); + + this.onValueChanged = properties.onValueChanged || function () {}; + + var widget = this; + this.addAction('onClick', function (event) { + widget.setChecked(!widget.isChecked()); + }); + this.checkMark.addAction('onClick', function (event) { + widget.setChecked(!widget.isChecked()); + }); }; +Checkbox.prototype = new Box(); +Checkbox.prototype.constructor = Checkbox; +Checkbox.prototype.toString = function () { + return "[UI.Checkbox " + this.id + "]"; +} +Checkbox.prototype.isChecked = function () { + return this.checked; +} +Checkbox.prototype.setChecked = function (value) { + this.checked = value; + this.checkMark.setVisible(this.checked); - - - - - - -// New layout functions - + this.onValueChanged(value); + UI.updateLayout(); +} +Checkbox.prototype.applyLayout = function () { + this.checkMark && this.checkMark.setPosition( + this.position.x + (this.width - this.checkMark.width) * 0.5, + this.position.y + (this.height - this.checkMark.height) * 0.5 + ); +} Icon.prototype.getWidth = function () { @@ -627,13 +685,15 @@ WidgetStack.prototype.applyLayout = function () { }, this); } WidgetStack.prototype.updateOverlays = function () { - this.backgroundOverlay.update({ - width: this.getWidth(), - height: this.getHeight(), - x: this.position.x, - y: this.position.y, - visible: this.isVisible() - }); + if (this.backgroundOverlay) { + this.backgroundOverlay.update({ + width: this.getWidth(), + height: this.getHeight(), + x: this.position.x, + y: this.position.y, + visible: this.isVisible() + }); + } } UI.addAttachment = function (target, rel, update) { attachment = { @@ -647,6 +707,9 @@ UI.addAttachment = function (target, rel, update) { UI.updateLayout = function () { + if (ui.visible) + ui.updateDebugUI(); + // Recalc dimensions ui.widgetList.forEach(function (widget) { widget.clearLayout(); @@ -724,7 +787,8 @@ function makeStatusWidget(defaultText, alpha) { height: statusDim.y, color: COLOR_WHITE, alpha: alpha || 0.98, - backgroundAlpha: 0.0 + backgroundAlpha: 0.0, + visible: ui.debug.visible }); label.updateText = function (text) { label.getOverlay().update({ @@ -735,70 +799,109 @@ function makeStatusWidget(defaultText, alpha) { statusPos.y += statusDim.y; return label; } + +ui.debug = {}; +ui.debug.visible = false; ui.focusStatus = makeStatusWidget(""); ui.eventStatus = makeStatusWidget("", 0.85); -(function () { - var eventList = []; - var maxEvents = 8; - ui.logEvent = function (event) { - eventList.push(event); - if (eventList.length >= maxEvents) - eventList.shift(); - ui.eventStatus.updateText(eventList.join('\n')); +UI.debug = { + eventList: { + position: { x: 20, y: 20 }, width: 1, height: 1 + }, + widgetList: { + position: { x: 500, y: 20 }, width: 1, height: 1 + }, + setVisible: function (visible) { + if (ui.debug.visible != visible) { + ui.focusStatus.setVisible(visible); + ui.eventStatus.setVisible(visible); + if (visible) { + ui.debug.showWidgetList(UI.debug.widgetList.position.x, UI.debug.widgetList.position.y); + } else { + ui.debug.hideWidgetList(); + } + UI.updateLayout(); + } + ui.debug.visible = visible; + }, + isVisible: function () { + return ui.debug.visible; } -})(); +} // Add debug list of all widgets + attachments -(function () { - var widgetListHeader = makeStatusWidget("Widgets: ", 0.98); - var widgetList = makeStatusWidget("", 0.85); - var attachmentListHeader = makeStatusWidget("Attachments: ", 0.98); - var attachmentList = makeStatusWidget("", 0.85); - var lineHeight = 20; +var widgetListHeader = makeStatusWidget("Widgets: ", 0.98); +var widgetList = makeStatusWidget("", 0.85); +var attachmentListHeader = makeStatusWidget("Attachments: ", 0.98); +var attachmentList = makeStatusWidget("", 0.85); +var lineHeight = 20; - // widgetListHeader.getOverlay().update({ - // // color: { red: 0.1, green: 0.5, blue: 0.9 }, - // backgroundColor: { red: 120, green: 250, blue: 12 }, - // backgroundAlpha: 0.9 - // }); +ui.debug.relayout = function () { + var x = UI.debug.widgetList.position.x, y = UI.debug.widgetList.position.y; - function relayout (x, y) { - widgetListHeader.setPosition(x, y); y += lineHeight; - widgetList.updateText(ui.widgetList.map(function (widget) { - return "" + widget + " actions: " + (Object.keys(widget.actions).join(", ") || "none"); - }).join("\n") || "no widgets"); - widgetList.setPosition(x, y); - y += lineHeight * (ui.widgetList.length || 1); + widgetListHeader.setPosition(x, y); y += lineHeight; + widgetList.updateText(ui.widgetList.map(function (widget) { + return "" + widget + " actions: " + (Object.keys(widget.actions).join(", ") || "none"); + }).join("\n") || "no widgets"); + widgetList.setPosition(x, y); + y += lineHeight * (ui.widgetList.length || 1); - attachmentListHeader.setPosition(x, y); y += lineHeight; - attachmentList.updateText(ui.attachmentList.map(function (attachment) { - return "[attachment target: " + attachment.target + ", to: " + attachment.rel + "]"; - }).join('\n') || "no attachments"); - attachmentList.setPosition(x, y); - y += lineHeight * (ui.widgetList.length || 1); + attachmentListHeader.setPosition(x, y); y += lineHeight; + attachmentList.updateText(ui.attachmentList.map(function (attachment) { + return "[attachment target: " + attachment.target + ", to: " + attachment.rel + "]"; + }).join('\n') || "no attachments"); + attachmentList.setPosition(x, y); + y += lineHeight * (ui.widgetList.length || 1); + // UI.updateLayout(); +} - UI.updateLayout(); - } +var defaultX = 500; +var defaultY = 20; +ui.debug.showWidgetList = function (x, y) { + widgetListHeader.setVisible(true); + widgetList.setVisible(true); + attachmentListHeader.setVisible(true); + attachmentList.setVisible(true); + ui.debug.relayout(x || defaultX, y || defaultY); +} +ui.debug.hideWidgetList = function () { + widgetListHeader.setVisible(false); + widgetList.setVisible(false); + attachmentListHeader.setVisible(false); + attachmentList.setVisible(false); +} + +ui.eventStatus.lastPos = { + x: ui.eventStatus.position.x, + y: ui.eventStatus.position.y +}; +ui.updateDebugUI = function () { + ui.debug.relayout(); + + var dx = ui.eventStatus.position.x - ui.eventStatus.lastPos.x; + var dy = ui.eventStatus.position.y - ui.eventStatus.lastPos.y; + + ui.focusStatus.position.x += dx; + ui.focusStatus.position.y += dy; + ui.eventStatus.position.x += dx; + ui.eventStatus.position.y += dy; + + ui.eventStatus.lastPos.x = ui.eventStatus.position.x; + ui.eventStatus.lastPos.y = ui.eventStatus.position.y; +} + +var eventList = []; +var maxEvents = 8; + +ui.logEvent = function (event) { + eventList.push(event); + if (eventList.length >= maxEvents) + eventList.shift(); + ui.eventStatus.updateText(eventList.join('\n')); +} - var defaultX = 500; - var defaultY = 20; - UI.showWidgetList = function (x, y) { - widgetListHeader.setVisible(true); - widgetList.setVisible(true); - attachmentListHeader.setVisible(true); - attachmentList.setVisible(true); - relayout(x || defaultX, y || defaultY); - } - UI.hideWidgetList = function () { - widgetListHeader.setVisible(false); - widgetList.setVisible(false); - attachmentListHeader.setVisible(false); - attachmentList.setVisible(true); - } -})(); -UI.showWidgetList(); // Tries to find a widget with an overlay matching overlay. // Used by getFocusedWidget(), dispatchEvent(), etc @@ -841,13 +944,16 @@ var dispatchEvent = function (action, event, widget) { } } -UI.handleMouseMove = function (event) { +UI.handleMouseMove = function (event, canStartDrag) { + if (canStartDrag === undefined) + canStartDrag = true; + // print("mouse moved x = " + event.x + ", y = " + event.y); var focused = getFocusedWidget(event); - print("got focus: " + focused); + // print("got focus: " + focused); - if (ui.clickedWidget && !ui.draggedWidget && ui.clickedWidget.actions['onDragBegin']) { + if (canStartDrag && !ui.draggedWidget && ui.clickedWidget && ui.clickedWidget.actions['onDragBegin']) { ui.draggedWidget = ui.clickedWidget; dispatchEvent('onDragBegin', event, ui.draggedWidget); } else if (ui.draggedWidget) { @@ -865,8 +971,8 @@ UI.handleMousePress = function (event) { print("Mouse clicked"); UI.handleMouseMove(event); ui.clickedWidget = ui.focusedWidget; - if (ui.focusedWidget) { - dispatchEvent('onMouseDown', event, ui.focusedWidget); + if (ui.clickedWidget) { + dispatchEvent('onMouseDown', event, ui.clickedWidget); } } @@ -876,7 +982,7 @@ UI.handleMouseRelease = function (event) { if (ui.draggedWidget) { dispatchEvent('onDragEnd', event, ui.draggedWidget); } else { - UI.handleMouseMove(event); + UI.handleMouseMove(event, false); if (ui.focusedWidget) { dispatchEvent('onMouseUp', event, ui.focusedWidget); From c175fe9e042e1abba0e3c78dba9e1581582e8534 Mon Sep 17 00:00:00 2001 From: Seiji Emery Date: Wed, 12 Aug 2015 15:48:21 -0700 Subject: [PATCH 50/53] moved example code to widgets-example.js --- examples/example/widgets-example.js | 442 ++++++++++ examples/games/solarsystem.js | 1207 --------------------------- 2 files changed, 442 insertions(+), 1207 deletions(-) create mode 100644 examples/example/widgets-example.js delete mode 100644 examples/games/solarsystem.js diff --git a/examples/example/widgets-example.js b/examples/example/widgets-example.js new file mode 100644 index 0000000000..8a4cea6ef0 --- /dev/null +++ b/examples/example/widgets-example.js @@ -0,0 +1,442 @@ +// +// widgets-example.js +// games +// +// Copyright 2015 High Fidelity, Inc. +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var paddingX = 8; +var paddingY = 8; +var buttonWidth = 30; +var buttonHeight = 30; + +var ICONS_URL = 'https://s3.amazonaws.com/hifi-public/marketplace/hificontent/Scripts/planets/images/'; + +var panelX = 1250; +var panelY = 500; +var panelWidth = 50; +var panelHeight = 210; + +// var mainPanel = new UIPanel(panelX, panelY, panelWidth, panelHeight); +// var systemViewButton = mainPanel.addIcon('solarsystems'); +// var zoomButton = mainPanel.addIcon('magnifier'); +// var satelliteButton = mainPanel.addIcon('satellite'); +// var settingsButton = mainPanel.addIcon('settings'); +// var stopButton = mainPanel.addIcon('close'); +// +// mainPanel.show(); +// +// var systemViewPanel = new UIPanel(panelX - 120, panelY, 120, 40); +// var reverseButton = systemViewPanel.addIcon('reverse'); +// var pauseButton = systemViewPanel.addIcon('playpause'); +// var forwardButton = systemViewPanel.addIcon('forward'); +// +// var zoomPanel = new UIPanel(panelX - 60, panelY + buttonHeight + paddingY, 650, 50); +// for (var i = 0; i < planets.length; ++i) { + // zoomPanel.addText(planets[i].name); +// } +Script.include('../libraries/uiwidgets.js'); + +UI.setDefaultVisibility(true); +UI.setErrorHandler(function(err) { + teardown(); + // print(err); + // Script.stop(); +}); + +// Controller.mouseMoveEvent.connect(function panelMouseMoveEvent(event) { return settings.mouseMoveEvent(event); }); +// Controller.mousePressEvent.connect( function panelMousePressEvent(event) { return settings.mousePressEvent(event); }); +// Controller.mouseDoublePressEvent.connect( function panelMouseDoublePressEvent(event) { return settings.mouseDoublePressEvent(event); }); +// Controller.mouseReleaseEvent.connect(function(event) { return settings.mouseReleaseEvent(event); }); +// Controller.keyPressEvent.connect(function(event) { return settings.keyPressEvent(event); }); + +// var ICON_WIDTH = 50.0; +// var ICON_HEIGHT = 50.0; +var ICON_WIDTH = 40.0; +var ICON_HEIGHT = 40.0; +var ICON_COLOR = UI.rgba(45, 45, 45, 0.7); +var FOCUSED_COLOR = UI.rgba(250, 250, 250, 1.0); + +var PANEL_BACKGROUND_COLOR = UI.rgba(50, 50, 50, 0.7); + +var PANEL_PADDING = 7.0; +var PANEL_BORDER = 12.0; +var SUBPANEL_GAP = 1.0; + + + + +var icons = []; +function addIcon(panel, iconId) { + var icon = panel.add(new UI.Icon({ + 'imageURL': ICONS_URL + iconId + '.svg', + 'width': ICON_WIDTH, + 'height': ICON_HEIGHT, + 'color': ICON_COLOR, + 'alpha': ICON_COLOR.a + })); + icons.push(icon); + return icon; +} + +var panels = []; +function addPanel (properties) { + properties.background = properties.background || {}; + properties.background.backgroundColor = properties.background.backgroundColor || + PANEL_BACKGROUND_COLOR; + properties.background.backgroundAlpha = properties.background.backgroundAlpha || + PANEL_BACKGROUND_COLOR.a; + properties.padding = properties.padding || { x: PANEL_PADDING, y: PANEL_PADDING }; + properties.border = properties.border || { x: PANEL_BORDER, y: PANEL_BORDER }; + + var panel = new UI.WidgetStack(properties); + panels.push(panel); + return panel; +} + +function makeDraggable (panel, target) { + if (!target) + target = panel; + + var dragStart = null; + var initialPos = null; + + panel.addAction('onDragBegin', function (event) { + dragStart = { x: event.x, y: event.y }; + initialPos = { x: target.position.x, y: target.position.y }; + }); + panel.addAction('onDragUpdate', function (event) { + target.setPosition( + initialPos.x + event.x - dragStart.x, + initialPos.y + event.y - dragStart.y + ); + UI.updateLayout(); + }); + panel.addAction('onDragEnd', function () { + dragStart = dragEnd = null; + }); +} + +// var panelContainer = new UI.WidgetContainer(); +// panelContainer.setPosition(500, 250); +// panelContainer.setVisible(true); + +var demoPane = addPanel({ dir: '+y' }); +var demoLabel = demoPane.add(new UI.Label({ + text: "< no events >", + width: 400, height: 20 +})); +var demoButton = demoPane.add(new UI.Box({ + width: 200, height: 80, + text: "Button" +})); +function setText(text) { + return function () { + demoLabel.setText(text); + UI.updateLayout(); + }; +} +function addDebugActions(widget, msg, actions) { + actions.forEach(function(action) { + widget.addAction(action, setText(action + " " + msg + widget)); + }); +} + +var debugEvents = [ + 'onMouseOver', + 'onMouseExit', + 'onMouseDown', + 'onMouseUp', + 'onDragBegin', + 'onDragEnd', + 'onDragUpdate' +]; +addDebugActions(demoPane, "(container) ", debugEvents); +addDebugActions(demoButton, "(button) ", debugEvents); +addDebugActions(demoLabel, "(label) ", debugEvents); + +// demoPane.addAction('onMouseOver', setText("onMouseOver " + demoPane)); +// demoPane.addAction('onMouseExit', setText("onMouseExit " + demoPane)); +// demoPane.addAction('onMouseDown', setText("onMouseDown " + demoPane)); +// demoPane.addAction('onMouseUp', setText("onMouseUp " + demoPane)); +makeDraggable(demoPane, demoPane); +demoPane.setPosition(600, 200); + +// demoButton.addAction('onMouseOver', setText("onMouseOver " + demoButton)); +// demoButton.addAction('onMouseExit', setText("onMouseExit " + demoButton)); +// demoButton.addAction() + +// var resizablePanel = new UI.Label({ +// text: "Resizable panel", +// width: 200, height: 200, +// backgroundAlpha: 0.5 +// }); +// resizablePanel.setPosition(1100, 200); + +var debugToggle = new UI.Box({ + text: "debug", width: 150, height: 20 +}); +debugToggle.setPosition(200, 0); +debugToggle.addAction('onClick', function () { + UI.debug.setVisible(!UI.debug.isVisible()); +}); + +// debugEvents.forEach(function (action) { +// resizablePanel.addAction(action, function (event, widget) { +// widget.setText(action + " " + widget); +// }); +// }) + +function join(obj) { + var s = "{"; + var sep = "\n"; + for (var k in obj) { + s += sep + k + ": " + (""+obj[k]).replace("\n", "\n"); + sep = ",\n"; + } + if (s.length > 1) + return s + " }"; + return s + "}"; +} + +// resizablePanel.getOverlay().update({ +// text: "" + join(resizablePanel.actions) +// }); + + +setText = addDebugActions = undefined; + + +var tooltipWidget = new UI.Label({ + text: "", + width: 500, height: 20, + visible: false +}); +function addTooltip (widget, text) { + widget.addAction('onMouseOver', function (event, widget) { + tooltipWidget.setVisible(true); + tooltipWidget.setPosition(widget.position.x + widget.getWidth() + 20, widget.position.y); + tooltipWidget.setText(text); + UI.updateLayout(); + }); + widget.addAction('onMouseExit', function () { + tooltipWidget.setVisible(false); + UI.updateLayout(); + }); +} + +var mainPanel = addPanel({ dir: '+y' }); +mainPanel.setPosition(500, 250); +mainPanel.setVisible(true); + +var systemViewButton = addIcon(mainPanel, 'solarsystems'); +var zoomButton = addIcon(mainPanel, 'magnifier'); +var satelliteButton = addIcon(mainPanel, 'satellite'); +var settingsButton = addIcon(mainPanel, 'settings'); +var stopButton = addIcon(mainPanel, 'close'); + + +addTooltip(systemViewButton, "system view"); +addTooltip(zoomButton, "zoom"); +addTooltip(satelliteButton, "satelite view"); +addTooltip(settingsButton, "settings"); +addTooltip(stopButton, "exit"); + +var systemViewPanel = addPanel({ dir: '+x', visible: false }); +var reverseButton = addIcon(systemViewPanel, 'reverse'); +var pauseButton = addIcon(systemViewPanel, 'playpause'); +var forwardButton = addIcon(systemViewPanel, 'forward'); + +var zoomPanel = addPanel({ dir: '+y', visible: true }); +var label = new UI.Label({ + text: "Foo", + width: 120, + height: 15, + color: UI.rgb(245, 290, 20), + alpha: 1.0, + backgroundColor: UI.rgb(10, 10, 10), + backgroundAlpha: 0.0 +}); +zoomPanel.add(label); +label.addAction('onMouseOver', function () { + label.setText("Bar"); + UI.updateLayout(); +}); +label.addAction('onMouseExit', function () { + label.setText("Foo"); + UI.updateLayout(); +}); +label.setText("Label id: " + label.id + ", parent id " + label.parent.id); +label.parent.addAction('onMouseOver', function () { + label.setText("on parent"); + UI.updateLayout(); +}); +label.parent.addAction('onMouseExit', function () { + label.setText('exited parent'); + UI.updateLayout(); +}); + +var sliderLayout = zoomPanel.add(new UI.WidgetStack({ + dir: '+x', visible: true, backgroundAlpha: 0.0 +})); +var sliderLabel = sliderLayout.add(new UI.Label({ + text: " ", width: 45, height: 20 +})); +var slider = sliderLayout.add(new UI.Slider({ + value: 10, maxValue: 100, minValue: 0, + width: 300, height: 20, + backgroundColor: UI.rgb(10, 10, 10), + backgroundAlpha: 1.0, + slider: { // slider knob + width: 30, + height: 18, + backgroundColor: UI.rgb(120, 120, 120), + backgroundAlpha: 1.0 + } +})); +sliderLabel.setText("" + (+slider.getValue().toFixed(1))); +slider.onValueChanged = function (value) { + sliderLabel.setText("" + (+value.toFixed(1))); + UI.updateLayout(); +} + + + + +var checkBoxLayout = zoomPanel.add(new UI.WidgetStack({ + dir: '+x', visible: true, backgroundAlpha: 0.0 +})); +// var padding = checkBoxLayout.add(new UI.Label({ +// text: " ", width: 45, height: 20 +// })); +var checkBoxLabel = checkBoxLayout.add(new UI.Label({ + text: "set red", width: 60, height: 20, + backgroundAlpha: 0.0 +})); +checkBoxLabel.setText("set red"); + +var defaultColor = UI.rgb(10, 10, 10); +var redColor = UI.rgb(210, 80, 80); + +var checkbox = checkBoxLayout.add(new UI.Checkbox({ + width: 20, height: 20, padding: { x: 3, y: 3 }, + backgroundColor: defaultColor, + backgroundAlpha: 0.9, + checked: false, + onValueChanged: function (red) { + zoomPanel.getOverlay().update({ + // backgroundAlpha: 0.1, + backgroundColor: red ? redColor : defaultColor + }); +} +})); + +addIcon(zoomPanel, 'reverse'); +UI.updateLayout(); + +var subpanels = [ systemViewPanel, zoomPanel ]; +function hideSubpanelsExcept (panel) { + subpanels.forEach(function (x) { + if (x != panel) { + x.setVisible(false); + } + }); +} + +function attachPanel (panel, button) { + button.addAction('onClick', function () { + hideSubpanelsExcept(panel); + panel.setVisible(!panel.isVisible()); + UI.updateLayout(); + }) + + UI.addAttachment(panel, button, function (target, rel) { + target.setPosition( + rel.position.x - (target.getWidth() + target.border.x + SUBPANEL_GAP), + rel.position.y - target.border.y + ); + }); +} +attachPanel(systemViewPanel, systemViewButton); +attachPanel(zoomPanel, zoomButton); + +var addColorToggle = function (widget) { + widget.addAction('onMouseOver', function () { + widget.setColor(FOCUSED_COLOR); + }); + widget.addAction('onMouseExit', function () { + widget.setColor(ICON_COLOR); + }); +} + +reverseButton.addAction('onClick', function() {}); + +systemViewPanel.addAction('onMouseOver', function() { + hideSubpanels(); + UI.updateLayout(); +}); + + +zoomButton.addAction('onClick', function() { + hideSubpanels(); + UI.updateLayout(); +}); +UI.updateLayout(); + +stopButton.addAction('onClick', function() { + // Script.stop(); + teardown(); +}); + +// Panel drag behavior +// (click + drag on border to drag) +(function () { + var dragged = null; + this.startDrag = function (dragAction) { + dragged = dragAction; + } + this.updateDrag = function (event) { + if (dragged) { + print("Update drag"); + dragged.updateDrag(event); + } + } + this.clearDrag = function (event) { + if (dragged) + print("End drag"); + dragged = null; + } +})(); + +var buttons = icons; + +buttons.map(addColorToggle); +panels.map(function (panel) { makeDraggable(panel, mainPanel); }); + +// Cleanup script resources +function teardown() { + UI.teardown(); + + // etc... +}; + +var inputHandler = { + onMouseMove: function (event) { + updateDrag(event); + UI.handleMouseMove(event); + }, + onMousePress: function (event) { + UI.handleMousePress(event); + }, + onMouseRelease: function (event) { + clearDrag(event); + UI.handleMouseRelease(event); + } +}; +Controller.mousePressEvent.connect(inputHandler.onMousePress); +Controller.mouseMoveEvent.connect(inputHandler.onMouseMove); +Controller.mouseReleaseEvent.connect(inputHandler.onMouseRelease); + +Script.scriptEnding.connect(teardown); diff --git a/examples/games/solarsystem.js b/examples/games/solarsystem.js deleted file mode 100644 index 65029c174d..0000000000 --- a/examples/games/solarsystem.js +++ /dev/null @@ -1,1207 +0,0 @@ -// -// solarsystem.js -// games -// -// Created by Bridget Went, 5/28/15. -// Copyright 2015 High Fidelity, Inc. -// -// The start to a project to build a virtual physics classroom to simulate the solar system, gravity, and orbital physics. -// - A sun with oribiting planets is created in front of the user -// - UI elements allow for adjusting the period, gravity, trails, and energy recalculations -// - Click "PAUSE" to pause the animation and show planet labels -// - In this mode, double-click a planet label to zoom in on that planet -// -Double-clicking on earth label initiates satellite orbiter game -// -Press "TAB" to toggle back to solar system view -// -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -// Script.include([ -// 'games/satellite.js' -// ]); - -var DAMPING = 0.0; -var LIFETIME = 6000; -var BASE_URL = "https://s3.amazonaws.com/hifi-public/marketplace/hificontent/Scripts/planets/planets/"; - -// Save intiial avatar and camera position -var startingPosition = { - x: 8000, - y: 8000, - z: 8000 -}; -MyAvatar.position = startingPosition; -var cameraStart = Camera.getOrientation(); - - -// Place the sun -var MAX_RANGE = 80.0; -var center = Vec3.sum(startingPosition, Vec3.multiply(MAX_RANGE, Vec3.normalize(Quat.getFront(Camera.getOrientation())))); -var SUN_SIZE = 7.0; - -var theSun = Entities.addEntity({ - type: "Model", - modelURL: BASE_URL + "sun.fbx", - position: center, - dimensions: { - x: SUN_SIZE, - y: SUN_SIZE, - z: SUN_SIZE - }, - angularVelocity: { - x: 0.0, - y: 0.1, - z: 0.0 - }, - angularDamping: DAMPING, - damping: DAMPING, - ignoreCollisions: false, - lifetime: LIFETIME, - collisionsWillMove: false -}); - - - -// Reference values for physical constants -var referenceRadius = 15.0; -var referenceDiameter = 0.6; -var referencePeriod = 3.0; -var LARGE_BODY_MASS = 250.0; -var SMALL_BODY_MASS = LARGE_BODY_MASS * 0.000000333; -var GRAVITY = (Math.pow(referenceRadius, 3.0) / Math.pow((referencePeriod / (2.0 * Math.PI)), 2.0)) / LARGE_BODY_MASS; -var REFERENCE_GRAVITY = GRAVITY; - -var planets = []; -var planetCount = 0; - -var TRAILS_ENABLED = false; -var MAX_POINTS_PER_LINE = 10; -var LINE_DIM = 200; -var LINE_WIDTH = 20; - -var VELOCITY_OFFSET_Y = -0.3; -var VELOCITY_OFFSET_Z = 0.9; - -var index = 0; - -var Planet = function(name, trailColor, radiusScale, sizeScale) { - - this.name = name; - this.trailColor = trailColor; - - this.trail = []; - this.lineStack = []; - - this.radius = radiusScale * referenceRadius; - this.position = Vec3.sum(center, { - x: this.radius, - y: 0.0, - z: 0.0 - }); - this.period = (2.0 * Math.PI) * Math.sqrt(Math.pow(this.radius, 3.0) / (GRAVITY * LARGE_BODY_MASS)); - this.gravity = GRAVITY; - this.initialVelocity = Math.sqrt((GRAVITY * LARGE_BODY_MASS) / this.radius); - this.velocity = Vec3.multiply(this.initialVelocity, Vec3.normalize({ - x: 0, - y: VELOCITY_OFFSET_Y, - z: VELOCITY_OFFSET_Z - })); - this.dimensions = sizeScale * referenceDiameter; - - this.planet = Entities.addEntity({ - type: "Model", - modelURL: BASE_URL + name + ".fbx", - position: this.position, - dimensions: { - x: this.dimensions, - y: this.dimensions, - z: this.dimensions - }, - velocity: this.velocity, - angularDamping: DAMPING, - damping: DAMPING, - ignoreCollisions: false, - lifetime: LIFETIME, - collisionsWillMove: true, - }); - - this.computeAcceleration = function() { - var acc = -(this.gravity * LARGE_BODY_MASS) * Math.pow(this.radius, (-2.0)); - return acc; - }; - - this.update = function(timeStep) { - var between = Vec3.subtract(this.position, center); - var speed = this.computeAcceleration(this.radius) * timeStep; - var vel = Vec3.multiply(speed, Vec3.normalize(between)); - - // Update velocity and position - this.velocity = Vec3.sum(this.velocity, vel); - this.position = Vec3.sum(this.position, Vec3.multiply(timeStep, this.velocity)); - Entities.editEntity(this.planet, { - velocity: this.velocity, - position: this.position - }); - - if (TRAILS_ENABLED) { - this.updateTrail(); - } - }; - - this.resetTrails = function() { - elapsed = 0.0; - - this.trail = []; - this.lineStack = []; - //add the first line to both the line entity stack and the trail - this.trail.push(newLine(this.lineStack, this.position, this.period, this.trailColor)); - - }; - - this.updateTrail = function() { - var point = this.position; - var linePos = Entities.getEntityProperties(this.lineStack[this.lineStack.length - 1]).position; - - this.trail.push(computeLocalPoint(linePos, point)); - - Entities.editEntity(this.lineStack[this.lineStack.length - 1], { - linePoints: this.trail - }); - - if (this.trail.length == MAX_POINTS_PER_LINE) { - this.trail = newLine(this.lineStack, point, this.period, this.trailColor); - } - }; - - this.zoom = function() { - var offset = { - x: 0.0, - y: 0.0, - z: 1.2 * sizeScale, - }; - MyAvatar.position = Vec3.sum(offset, this.position); - }; - - this.adjustPeriod = function(alpha) { - // Update global G constant, period, poke velocity to new value - var ratio = this.last_alpha / alpha; - this.gravity = Math.pow(ratio, 2.0) * GRAVITY; - this.period = ratio * this.period; - this.velocity = Vec3.multiply(ratio, this.velocity); - - this.last_alpha = alpha; - }; - - - index++; - this.resetTrails(); - -} - - -var MERCURY_LINE_COLOR = { - red: 255, - green: 255, - blue: 255 -}; -var VENUS_LINE_COLOR = { - red: 255, - green: 160, - blue: 110 -}; -var EARTH_LINE_COLOR = { - red: 10, - green: 150, - blue: 160 -}; -var MARS_LINE_COLOR = { - red: 180, - green: 70, - blue: 10 -}; -var JUPITER_LINE_COLOR = { - red: 250, - green: 140, - blue: 0 -}; -var SATURN_LINE_COLOR = { - red: 235, - green: 215, - blue: 0 -}; -var URANUS_LINE_COLOR = { - red: 135, - green: 205, - blue: 240 -}; -var NEPTUNE_LINE_COLOR = { - red: 30, - green: 140, - blue: 255 -}; -var PLUTO_LINE_COLOR = { - red: 255, - green: 255, - blue: 255 -}; - -planets.push(new Planet("mercury", MERCURY_LINE_COLOR, 0.387, 0.383)); -planets.push(new Planet("venus", VENUS_LINE_COLOR, 0.723, 0.949)); -planets.push(new Planet("earth", EARTH_LINE_COLOR, 1.0, 1.0)); -planets.push(new Planet("mars", MARS_LINE_COLOR, 1.52, 0.532)); -planets.push(new Planet("jupiter", JUPITER_LINE_COLOR, 5.20, 11.21)); -planets.push(new Planet("saturn", SATURN_LINE_COLOR, 9.58, 9.45)); -planets.push(new Planet("uranus", URANUS_LINE_COLOR, 19.20, 4.01)); -planets.push(new Planet("neptune", NEPTUNE_LINE_COLOR, 30.05, 3.88)); -planets.push(new Planet("pluto", PLUTO_LINE_COLOR, 39.48, 0.186)); - -var LABEL_X = 8.0; -var LABEL_Y = 3.0; -var LABEL_Z = 1.0; -var LABEL_DIST = 8.0; -var TEXT_HEIGHT = 1.0; - -var PlanetLabel = function(name, index) { - var text = name + " Speed: " + Vec3.length(planets[index].velocity).toFixed(2); - this.labelPos = Vec3.sum(planets[index].position, { - x: 0.0, - y: LABEL_DIST, - z: LABEL_DIST - }); - this.linePos = planets[index].position; - - this.line = Entities.addEntity({ - type: "Line", - position: this.linePos, - dimensions: { - x: 20, - y: 20, - z: 20 - }, - lineWidth: LINE_WIDTH, - color: { - red: 255, - green: 255, - blue: 255 - }, - linePoints: [{ - x: 0, - y: 0, - z: 0 - }, - computeLocalPoint(this.linePos, this.labelPos) - ], - visible: false - }); - - this.label = Entities.addEntity({ - type: "Text", - text: text, - lineHeight: TEXT_HEIGHT, - dimensions: { - x: LABEL_X, - y: LABEL_Y, - z: LABEL_Z - }, - position: this.labelPos, - backgroundColor: { - red: 10, - green: 10, - blue: 10 - }, - faceCamera: true, - visible: false - }); - - this.show = function() { - this.showing = true; - Entities.editEntity(this.line, { - visible: true - }); - Entities.editEntity(this.label, { - visible: true - }); - } - - this.hide = function() { - this.showing = false; - Entities.editEntity(this.line, { - visible: false - }); - Entities.editEntity(this.label, { - visible: false - }); - } -} - - -var time = 0.0; -var elapsed; -var TIME_STEP = 60.0; -var dt = 1.0 / TIME_STEP; - -var planetLines = []; -var trails = []; -var paused = false; - -function update(deltaTime) { - if (paused) { - return; - } - //deltaTime = dt; - time++; - if (time % TIME_STEP == 0) { - elapsed++; - } - - for (var i = 0; i < planets.length; ++i) { - planets[i].update(deltaTime); - } -} - -function pause() { - for (var i = 0; i < planets.length; ++i) { - Entities.editEntity(planets[i].planet, { - velocity: { - x: 0.0, - y: 0.0, - z: 0.0 - } - }); - planets[i].label = new PlanetLabel(planets[i].name, i); - planets[i].label.show(); - } - paused = true; -} - -function resume() { - for (var i = 0; i < planets.length; ++i) { - planets[i].label.hide(); - } - paused = false; -} - -function computeLocalPoint(linePos, worldPoint) { - var localPoint = Vec3.subtract(worldPoint, linePos); - return localPoint; -} - -// Create a new line -function newLine(lineStack, point, period, color) { - if (elapsed < period) { - var line = Entities.addEntity({ - position: point, - type: "Line", - color: color, - dimensions: { - x: LINE_DIM, - y: LINE_DIM, - z: LINE_DIM - }, - lifetime: LIFETIME, - lineWidth: LINE_WIDTH - }); - lineStack.push(line); - } else { - // Begin overwriting first lines after one full revolution (one period) - var firstLine = lineStack.shift(); - Entities.editEntity(firstLine, { - position: point, - linePoints: [{ - x: 0.0, - y: 0.0, - z: 0.0 - }] - }); - lineStack.push(firstLine); - - } - var points = []; - points.push(computeLocalPoint(point, point)); - return points; -} - - -var paddingX = 8; -var paddingY = 8; -var buttonWidth = 30; -var buttonHeight = 30; - -var ICONS_URL = 'https://s3.amazonaws.com/hifi-public/marketplace/hificontent/Scripts/planets/images/'; - -// var UIPanel = function(x, y, width, height) { -// this.visible = false; -// this.buttons = []; -// this.x = x; -// this.y = y; -// this.offsetX = paddingX; -// this.offsetY = paddingY; - -// this.background = Overlays.addOverlay("text", { -// backgroundColor: { -// red: 240, -// green: 240, -// blue: 255 -// }, -// textColor: { -// red: 10, -// green: 10, -// blue: 20 -// }, -// x: this.x, -// y: this.y, -// width: width, -// height: height, -// alpha: 1.0, -// backgroundAlpha: 0.7, -// visible: false -// }); - -// this.addIcon = function(iconID) { -// var icon = Overlays.addOverlay("image", { -// color: { -// red: 10, -// green: 10, -// blue: 10 -// }, -// imageURL: ICONS_URL + iconID + '.svg', -// x: this.x + this.offsetX, -// y: this.y + this.offsetY, -// width: buttonWidth, -// height: buttonHeight, -// alpha: 1.0, -// visible: false -// }); - -// if (width > height) { -// this.offsetX += buttonWidth + paddingX; -// } else { -// this.offsetY += buttonHeight + paddingY; -// } - - -// this.buttons.push(icon); -// return icon; -// } - -// this.addText = function(text) { -// var icon = Overlays.addOverlay("text", { -// color: { -// red: 240, -// green: 240, -// blue: 255 -// }, -// text: text, -// x: this.x + this.offsetX, -// y: this.y + this.offsetY, -// width: buttonWidth + paddingX * 4.0, -// height: buttonHeight, -// visible: false -// }); - -// if (width > height) { -// this.offsetX += buttonWidth + paddingX * 5.0; -// } else { -// this.offsetY += buttonHeight + paddingY * 5.0; -// } - - - -// this.buttons.push(icon); -// return icon; -// } - - -// this.show = function() { -// Overlays.editOverlay(this.background, { -// visible: true -// }); -// for (var i in this.buttons) { -// Overlays.editOverlay(this.buttons[i], { -// visible: true -// }); -// } -// this.visible = true; -// } - -// this.hide = function() { -// Overlays.editOverlay(this.background, { -// visible: false -// }); -// for (var i in this.buttons) { -// Overlays.editOverlay(this.buttons[i], { -// visible: false -// }); -// } -// this.visible = false; -// } - -// this.remove = function() { -// Overlays.deleteOverlay(this.background); -// for (var i in this.buttons) { -// Overlays.deleteOverlay(this.buttons[i]); -// } -// }; -// -// } - -var panelX = 1250; -var panelY = 500; -var panelWidth = 50; -var panelHeight = 210; - -// var mainPanel = new UIPanel(panelX, panelY, panelWidth, panelHeight); -// var systemViewButton = mainPanel.addIcon('solarsystems'); -// var zoomButton = mainPanel.addIcon('magnifier'); -// var satelliteButton = mainPanel.addIcon('satellite'); -// var settingsButton = mainPanel.addIcon('settings'); -// var stopButton = mainPanel.addIcon('close'); -// -// mainPanel.show(); -// -// var systemViewPanel = new UIPanel(panelX - 120, panelY, 120, 40); -// var reverseButton = systemViewPanel.addIcon('reverse'); -// var pauseButton = systemViewPanel.addIcon('playpause'); -// var forwardButton = systemViewPanel.addIcon('forward'); -// -// var zoomPanel = new UIPanel(panelX - 60, panelY + buttonHeight + paddingY, 650, 50); -// for (var i = 0; i < planets.length; ++i) { - // zoomPanel.addText(planets[i].name); -// } -Script.include('../libraries/uiwidgets.js'); - -UI.setDefaultVisibility(true); -UI.setErrorHandler(function(err) { - teardown(); - // print(err); - // Script.stop(); -}); - -// Controller.mouseMoveEvent.connect(function panelMouseMoveEvent(event) { return settings.mouseMoveEvent(event); }); -// Controller.mousePressEvent.connect( function panelMousePressEvent(event) { return settings.mousePressEvent(event); }); -// Controller.mouseDoublePressEvent.connect( function panelMouseDoublePressEvent(event) { return settings.mouseDoublePressEvent(event); }); -// Controller.mouseReleaseEvent.connect(function(event) { return settings.mouseReleaseEvent(event); }); -// Controller.keyPressEvent.connect(function(event) { return settings.keyPressEvent(event); }); - -// var ICON_WIDTH = 50.0; -// var ICON_HEIGHT = 50.0; -var ICON_WIDTH = 40.0; -var ICON_HEIGHT = 40.0; -var ICON_COLOR = UI.rgba(45, 45, 45, 0.7); -var FOCUSED_COLOR = UI.rgba(250, 250, 250, 1.0); - -var PANEL_BACKGROUND_COLOR = UI.rgba(50, 50, 50, 0.7); - -var PANEL_PADDING = 7.0; -var PANEL_BORDER = 12.0; -var SUBPANEL_GAP = 1.0; - - - - -var icons = []; -function addIcon(panel, iconId) { - var icon = panel.add(new UI.Icon({ - 'imageURL': ICONS_URL + iconId + '.svg', - 'width': ICON_WIDTH, - 'height': ICON_HEIGHT, - 'color': ICON_COLOR, - 'alpha': ICON_COLOR.a - })); - icons.push(icon); - return icon; -} - -var panels = []; -function addPanel (properties) { - properties.background = properties.background || {}; - properties.background.backgroundColor = properties.background.backgroundColor || - PANEL_BACKGROUND_COLOR; - properties.background.backgroundAlpha = properties.background.backgroundAlpha || - PANEL_BACKGROUND_COLOR.a; - properties.padding = properties.padding || { x: PANEL_PADDING, y: PANEL_PADDING }; - properties.border = properties.border || { x: PANEL_BORDER, y: PANEL_BORDER }; - - var panel = new UI.WidgetStack(properties); - panels.push(panel); - return panel; -} - -function makeDraggable (panel, target) { - if (!target) - target = panel; - - var dragStart = null; - var initialPos = null; - - panel.addAction('onDragBegin', function (event) { - dragStart = { x: event.x, y: event.y }; - initialPos = { x: target.position.x, y: target.position.y }; - }); - panel.addAction('onDragUpdate', function (event) { - target.setPosition( - initialPos.x + event.x - dragStart.x, - initialPos.y + event.y - dragStart.y - ); - UI.updateLayout(); - }); - panel.addAction('onDragEnd', function () { - dragStart = dragEnd = null; - }); -} - -// var panelContainer = new UI.WidgetContainer(); -// panelContainer.setPosition(500, 250); -// panelContainer.setVisible(true); - -var demoPane = addPanel({ dir: '+y' }); -var demoLabel = demoPane.add(new UI.Label({ - text: "< no events >", - width: 400, height: 20 -})); -var demoButton = demoPane.add(new UI.Box({ - width: 200, height: 80, - text: "Button" -})); -function setText(text) { - return function () { - demoLabel.setText(text); - UI.updateLayout(); - }; -} -function addDebugActions(widget, msg, actions) { - actions.forEach(function(action) { - widget.addAction(action, setText(action + " " + msg + widget)); - }); -} - -var debugEvents = [ - 'onMouseOver', - 'onMouseExit', - 'onMouseDown', - 'onMouseUp', - 'onDragBegin', - 'onDragEnd', - 'onDragUpdate' -]; -addDebugActions(demoPane, "(container) ", debugEvents); -addDebugActions(demoButton, "(button) ", debugEvents); -addDebugActions(demoLabel, "(label) ", debugEvents); - -// demoPane.addAction('onMouseOver', setText("onMouseOver " + demoPane)); -// demoPane.addAction('onMouseExit', setText("onMouseExit " + demoPane)); -// demoPane.addAction('onMouseDown', setText("onMouseDown " + demoPane)); -// demoPane.addAction('onMouseUp', setText("onMouseUp " + demoPane)); -makeDraggable(demoPane, demoPane); -demoPane.setPosition(600, 200); - -// demoButton.addAction('onMouseOver', setText("onMouseOver " + demoButton)); -// demoButton.addAction('onMouseExit', setText("onMouseExit " + demoButton)); -// demoButton.addAction() - -var resizablePanel = new UI.Label({ - text: "Resizable panel", - width: 200, height: 200, - backgroundAlpha: 0.5 -}); -resizablePanel.setPosition(1100, 200); - -var debugToggle = new UI.Box({ - text: "debug", width: 150, height: 20 -}); -debugToggle.setPosition(1000, 0); -debugToggle.addAction('onClick', function () { - UI.debug.setVisible(!UI.debug.isVisible()); -}) - - - - - -debugEvents.forEach(function (action) { - resizablePanel.addAction(action, function (event, widget) { - widget.setText(action + " " + widget); - }); -}) - -addDebugActions(resizablePanel, "", debugEvents); - -function join(obj) { - var s = "{"; - var sep = "\n"; - for (var k in obj) { - s += sep + k + ": " + (""+obj[k]).replace("\n", "\n"); - sep = ",\n"; - } - if (s.length > 1) - return s + " }"; - return s + "}"; -} - -// resizablePanel.getOverlay().update({ -// text: "" + join(resizablePanel.actions) -// }); - - -setText = addDebugActions = undefined; - - -var tooltipWidget = new UI.Label({ - text: "", - width: 500, height: 20, - visible: false -}); -function addTooltip (widget, text) { - widget.addAction('onMouseOver', function (event, widget) { - tooltipWidget.setVisible(true); - tooltipWidget.setPosition(widget.position.x + widget.getWidth() + 20, widget.position.y); - tooltipWidget.setText(text); - UI.updateLayout(); - }); - widget.addAction('onMouseExit', function () { - tooltipWidget.setVisible(false); - UI.updateLayout(); - }); -} - -var mainPanel = addPanel({ dir: '+y' }); -mainPanel.setPosition(500, 250); -mainPanel.setVisible(true); - -var systemViewButton = addIcon(mainPanel, 'solarsystems'); -var zoomButton = addIcon(mainPanel, 'magnifier'); -var satelliteButton = addIcon(mainPanel, 'satellite'); -var settingsButton = addIcon(mainPanel, 'settings'); -var stopButton = addIcon(mainPanel, 'close'); - - -addTooltip(systemViewButton, "system view"); -addTooltip(zoomButton, "zoom"); -addTooltip(satelliteButton, "satelite view"); -addTooltip(settingsButton, "settings"); -addTooltip(stopButton, "exit"); - - -var systemViewPanel = addPanel({ dir: '+x', visible: false }); -var reverseButton = addIcon(systemViewPanel, 'reverse'); -var pauseButton = addIcon(systemViewPanel, 'playpause'); -var forwardButton = addIcon(systemViewPanel, 'forward'); - -var zoomPanel = addPanel({ dir: '+y', visible: true }); -var label = new UI.Label({ - text: "Foo", - width: 120, - height: 15, - color: UI.rgb(245, 290, 20), - alpha: 1.0, - backgroundColor: UI.rgb(10, 10, 10), - backgroundAlpha: 0.0 -}); -zoomPanel.add(label); -label.addAction('onMouseOver', function () { - label.setText("Bar"); - UI.updateLayout(); -}); -label.addAction('onMouseExit', function () { - label.setText("Foo"); - UI.updateLayout(); -}); -label.setText("Label id: " + label.id + ", parent id " + label.parent.id); -label.parent.addAction('onMouseOver', function () { - label.setText("on parent"); - UI.updateLayout(); -}); -label.parent.addAction('onMouseExit', function () { - label.setText('exited parent'); - UI.updateLayout(); -}); - -var sliderLayout = zoomPanel.add(new UI.WidgetStack({ - dir: '+x', visible: true, backgroundAlpha: 0.0 -})); -var sliderLabel = sliderLayout.add(new UI.Label({ - text: " ", width: 45, height: 20 -})); -var slider = sliderLayout.add(new UI.Slider({ - value: 10, maxValue: 100, minValue: 0, - width: 300, height: 20, - backgroundColor: UI.rgb(10, 10, 10), - backgroundAlpha: 1.0, - slider: { // slider knob - width: 30, - height: 18, - backgroundColor: UI.rgb(120, 120, 120), - backgroundAlpha: 1.0 - } -})); -sliderLabel.setText("" + (+slider.getValue().toFixed(1))); -slider.onValueChanged = function (value) { - sliderLabel.setText("" + (+value.toFixed(1))); - UI.updateLayout(); -} - - - - -var checkBoxLayout = zoomPanel.add(new UI.WidgetStack({ - dir: '+x', visible: true, backgroundAlpha: 0.0 -})); -// var padding = checkBoxLayout.add(new UI.Label({ -// text: " ", width: 45, height: 20 -// })); -var checkBoxLabel = checkBoxLayout.add(new UI.Label({ - text: "set red", width: 60, height: 20, - backgroundAlpha: 0.0 -})); -checkBoxLabel.setText("set red"); - -var defaultColor = UI.rgb(10, 10, 10); -var redColor = UI.rgb(210, 80, 80); - -var checkbox = checkBoxLayout.add(new UI.Checkbox({ - width: 20, height: 20, padding: { x: 3, y: 3 }, - backgroundColor: defaultColor, - backgroundAlpha: 0.9, - checked: false, - onValueChanged: function (red) { - zoomPanel.getOverlay().update({ - // backgroundAlpha: 0.1, - backgroundColor: red ? redColor : defaultColor - }); -} -})); - -// checkbox.onValueChanged = function (red) { -// zoomPanel.getOverlay().update({ -// // backgroundAlpha: 0.1, -// backgroundColor: red ? redColor : defaultColor -// }); -// } - - - - - -addIcon(zoomPanel, 'reverse'); - -UI.updateLayout(); - - -var subpanels = [ systemViewPanel, zoomPanel ]; -function hideSubpanelsExcept (panel) { - subpanels.forEach(function (x) { - if (x != panel) { - x.setVisible(false); - } - }); -} - -function attachPanel (panel, button) { - button.addAction('onClick', function () { - hideSubpanelsExcept(panel); - panel.setVisible(!panel.isVisible()); - UI.updateLayout(); - }) - - UI.addAttachment(panel, button, function (target, rel) { - target.setPosition( - rel.position.x - (target.getWidth() + target.border.x + SUBPANEL_GAP), - rel.position.y - target.border.y - ); - }); -} -attachPanel(systemViewPanel, systemViewButton); -attachPanel(zoomPanel, zoomButton); - -var addColorToggle = function (widget) { - widget.addAction('onMouseOver', function () { - widget.setColor(FOCUSED_COLOR); - }); - widget.addAction('onMouseExit', function () { - widget.setColor(ICON_COLOR); - }); -} - -reverseButton.addAction('onClick', function() { - -}); -systemViewPanel.addAction('onMouseOver', function() { - hideSubpanels(); - UI.updateLayout(); - // paused ? resume() : pause(); -}); - - -zoomButton.addAction('onClick', function() { - hideSubpanels(); - UI.updateLayout(); - - if (zoomButton.visible) { - MyAvatar.position = startingPosition; - Camera.setOrientation(cameraStart); - // resume(); - } else { - // pause(); - } -}); -UI.updateLayout(); - -stopButton.addAction('onClick', function() { - // Script.stop(); - teardown(); -}); - -// Panel drag behavior -// (click + drag on border to drag) -(function () { - var dragged = null; - this.startDrag = function (dragAction) { - dragged = dragAction; - } - this.updateDrag = function (event) { - if (dragged) { - print("Update drag"); - dragged.updateDrag(event); - } - } - this.clearDrag = function (event) { - if (dragged) - print("End drag"); - dragged = null; - } -})(); - - - -var buttons = icons; - -buttons.map(addColorToggle); -panels.map(function (panel) { makeDraggable(panel, mainPanel); }); - - - - -// Script.include('../utilities/tools/cookies.js'); -// var settings; - -// var satelliteView; -// var satelliteGame; - -// // satelliteButton.addAction('onclick', function() { -// // if (satelliteView) { -// // satelliteGame.endGame(); -// // MyAvatar.position = startingPosition; -// // satelliteView = false; -// // resume(); -// // } else { -// // pause(); -// // var confirmed = Window.confirm("Start satellite game?"); -// // if (!confirmed) { -// // resume(); -// // return; -// // } -// // satelliteView = true; -// // MyAvatar.position = { -// // x: 200, -// // y: 200, -// // z: 200 -// // }; -// // Camera.setPosition({ -// // x: 200, -// // y: 200, -// // z: 200 -// // }); -// // satelliteGame = new SatelliteGame(); -// // } -// // }); - -// settingsButton.addAction('onclick', function() { -// if (!settings) { -// settings = new Panel(panelX - 610, panelY - 130); -// settings.visible = false; -// var g_multiplier = 1.0; -// settings.newSlider("Gravitational Force ", 0.1, 5.0, -// function(value) { -// g_multiplier = value; -// GRAVITY = REFERENCE_GRAVITY * g_multiplier; -// }, - -// function() { -// return g_multiplier; -// }, -// function(value) { -// return value.toFixed(1) + "x"; -// }); - -// var subPanel = settings.newSubPanel("Orbital Periods"); -// for (var i = 0; i < planets.length; ++i) { -// var period_multiplier = 1.0; -// var last_alpha = period_multiplier; - -// subPanel.newSlider(planets[i].name, 0.1, 3.0, -// function(value) { -// period_multiplier = value; -// planets[i].adjustPeriod(value); -// }, -// function() { -// return period_multiplier; -// }, -// function(value) { -// return (value).toFixed(2) + "x"; -// }); -// } -// settings.newCheckbox("Leave Trails: ", -// function(value) { -// TRAILS_ENABLED = value; -// if (TRAILS_ENABLED) { -// for (var i = 0; i < planets.length; ++i) { -// planets[i].resetTrails(); -// } -// //if trails are off and we've already created trails, remove existing trails -// } else { -// for (var i = 0; i < planets.length; ++i) { -// for (var j = 0; j < planets[i].lineStack.length; ++j) { -// Entities.editEntity(planets[i].lineStack[j], { -// visible: false -// }); -// } -// planets[i].lineStack = []; -// } -// } -// }, -// function() { -// return TRAILS_ENABLED; -// }, -// function(value) { -// return value; -// }); -// } else { -// settings.destroy(); -// settings = null; -// } -// }); - - - - - - - - -// function mousePressEvent(event) { -// var clickedOverlay = Overlays.getOverlayAtPoint({ -// x: event.x, -// y: event.y -// }); - -// if (clickedOverlay == systemViewButton) { -// if (systemViewPanel.visible) { -// systemViewPanel.hide(); -// } else { -// systemViewPanel.show(); -// } -// } -// if (clickedOverlay == pauseButton) { -// if (!paused) { -// pause(); -// } else { -// resume(); -// } - -// } -// if (clickedOverlay == zoomButton) { -// if (zoomPanel.visible) { -// zoomPanel.hide(); -// MyAvatar.position = startingPosition; -// Camera.setOrientation(cameraStart); -// resume(); -// } else { -// zoomPanel.show(); -// pause(); -// } -// } -// var zoomed = false; -// if (zoomPanel.visible) { -// for (var i = 0; i < planets.length; ++i) { -// if (clickedOverlay == zoomPanel.buttons[i]) { -// pause(); -// planets[i].zoom(); -// zoomed = true; -// } -// } -// } - - -// if (clickedOverlay == satelliteButton) { - - -// } - -// if (clickedOverlay == settingsButton) { - -// } - -// if(clickedOverlay == stopButton) { -// Script.stop(); -// } -// } - -// UI.printWidgets(); - -// Clean up models, UI panels, lines, and button overlays -function teardown() { - UI.teardown(); - // mainPanel.remove(); - // systemViewPanel.remove(); - // zoomPanel.remove(); - // if (settings) { - // settings.destroy(); - // } - - Entities.deleteEntity(theSun); - for (var i = 0; i < planets.length; ++i) { - Entities.deleteEntity(planets[i].planet); - } - - var e = Entities.findEntities(MyAvatar.position, 16000); - for (i = 0; i < e.length; i++) { - var props = Entities.getEntityProperties(e[i]); - if (props.type === "Line" || props.type === "Text") { - Entities.deleteEntity(e[i]); - } - } -}; - -// Controller.mousePressEvent.connect(mousePressEvent); - -// if (settings) { -// Controller.mouseMoveEvent.connect(function panelMouseMoveEvent(event) { return settings.mouseMoveEvent(event); }); -// Controller.mousePressEvent.connect( function panelMousePressEvent(event) { return settings.mousePressEvent(event); }); -// Controller.mouseDoublePressEvent.connect( function panelMouseDoublePressEvent(event) { return settings.mouseDoublePressEvent(event); }); -// Controller.mouseReleaseEvent.connect(function(event) { return settings.mouseReleaseEvent(event); }); -// Controller.keyPressEvent.connect(function(event) { return settings.keyPressEvent(event); }); -// } - -var inputHandler = { - onMouseMove: function (event) { - updateDrag(event); - UI.handleMouseMove(event); - }, - onMousePress: function (event) { - UI.handleMousePress(event); - }, - onMouseRelease: function (event) { - clearDrag(event); - UI.handleMouseRelease(event); - } -}; -Controller.mousePressEvent.connect(inputHandler.onMousePress); -Controller.mouseMoveEvent.connect(inputHandler.onMouseMove); -Controller.mouseReleaseEvent.connect(inputHandler.onMouseRelease); - - - -Script.scriptEnding.connect(teardown); -Script.update.connect(update); \ No newline at end of file From 81588f7db0cf05b12b819d47f3f9102f2cfd1211 Mon Sep 17 00:00:00 2001 From: Seiji Emery Date: Wed, 12 Aug 2015 16:26:47 -0700 Subject: [PATCH 51/53] renamed Icon to Image --- examples/example/widgets-example.js | 41 +++--- examples/libraries/uiwidgets.js | 193 ++++++++++++++-------------- 2 files changed, 113 insertions(+), 121 deletions(-) diff --git a/examples/example/widgets-example.js b/examples/example/widgets-example.js index 8a4cea6ef0..eea7f51876 100644 --- a/examples/example/widgets-example.js +++ b/examples/example/widgets-example.js @@ -20,18 +20,18 @@ var panelWidth = 50; var panelHeight = 210; // var mainPanel = new UIPanel(panelX, panelY, panelWidth, panelHeight); -// var systemViewButton = mainPanel.addIcon('solarsystems'); -// var zoomButton = mainPanel.addIcon('magnifier'); -// var satelliteButton = mainPanel.addIcon('satellite'); -// var settingsButton = mainPanel.addIcon('settings'); -// var stopButton = mainPanel.addIcon('close'); +// var systemViewButton = mainPanel.addImage('solarsystems'); +// var zoomButton = mainPanel.addImage('magnifier'); +// var satelliteButton = mainPanel.addImage('satellite'); +// var settingsButton = mainPanel.addImage('settings'); +// var stopButton = mainPanel.addImage('close'); // // mainPanel.show(); // // var systemViewPanel = new UIPanel(panelX - 120, panelY, 120, 40); -// var reverseButton = systemViewPanel.addIcon('reverse'); -// var pauseButton = systemViewPanel.addIcon('playpause'); -// var forwardButton = systemViewPanel.addIcon('forward'); +// var reverseButton = systemViewPanel.addImage('reverse'); +// var pauseButton = systemViewPanel.addImage('playpause'); +// var forwardButton = systemViewPanel.addImage('forward'); // // var zoomPanel = new UIPanel(panelX - 60, panelY + buttonHeight + paddingY, 650, 50); // for (var i = 0; i < planets.length; ++i) { @@ -65,12 +65,9 @@ var PANEL_PADDING = 7.0; var PANEL_BORDER = 12.0; var SUBPANEL_GAP = 1.0; - - - var icons = []; -function addIcon(panel, iconId) { - var icon = panel.add(new UI.Icon({ +function addImage(panel, iconId) { + var icon = panel.add(new UI.Image({ 'imageURL': ICONS_URL + iconId + '.svg', 'width': ICON_WIDTH, 'height': ICON_HEIGHT, @@ -231,11 +228,11 @@ var mainPanel = addPanel({ dir: '+y' }); mainPanel.setPosition(500, 250); mainPanel.setVisible(true); -var systemViewButton = addIcon(mainPanel, 'solarsystems'); -var zoomButton = addIcon(mainPanel, 'magnifier'); -var satelliteButton = addIcon(mainPanel, 'satellite'); -var settingsButton = addIcon(mainPanel, 'settings'); -var stopButton = addIcon(mainPanel, 'close'); +var systemViewButton = addImage(mainPanel, 'solarsystems'); +var zoomButton = addImage(mainPanel, 'magnifier'); +var satelliteButton = addImage(mainPanel, 'satellite'); +var settingsButton = addImage(mainPanel, 'settings'); +var stopButton = addImage(mainPanel, 'close'); addTooltip(systemViewButton, "system view"); @@ -245,9 +242,9 @@ addTooltip(settingsButton, "settings"); addTooltip(stopButton, "exit"); var systemViewPanel = addPanel({ dir: '+x', visible: false }); -var reverseButton = addIcon(systemViewPanel, 'reverse'); -var pauseButton = addIcon(systemViewPanel, 'playpause'); -var forwardButton = addIcon(systemViewPanel, 'forward'); +var reverseButton = addImage(systemViewPanel, 'reverse'); +var pauseButton = addImage(systemViewPanel, 'playpause'); +var forwardButton = addImage(systemViewPanel, 'forward'); var zoomPanel = addPanel({ dir: '+y', visible: true }); var label = new UI.Label({ @@ -333,7 +330,7 @@ var checkbox = checkBoxLayout.add(new UI.Checkbox({ } })); -addIcon(zoomPanel, 'reverse'); +addImage(zoomPanel, 'reverse'); UI.updateLayout(); var subpanels = [ systemViewPanel, zoomPanel ]; diff --git a/examples/libraries/uiwidgets.js b/examples/libraries/uiwidgets.js index e0801e3a81..c53bb9ba65 100644 --- a/examples/libraries/uiwidgets.js +++ b/examples/libraries/uiwidgets.js @@ -157,16 +157,7 @@ UI.setDefaultVisibility = function (visible) { /// Wrapper around the overlays impl function makeOverlay(type, properties) { - var _TRACE = traceEnter.call(this, "makeOverlay"); var overlay = Overlays.addOverlay(type, properties); - // overlay.update = function (properties) { - // Overlays.editOverlay(overlay, properties); - // } - // overlay.destroy = function () { - // Overlays.deleteOverlay(overlay); - // } - // return overlay; - _TRACE.exit(); return { 'update': function (properties) { var _TRACE = traceEnter.call(this, "Overlay.update"); @@ -349,15 +340,72 @@ WidgetStack.prototype.setColor = function (color) { 'alpha': color.a }); } +var sumOf = function (list, f) { + var sum = 0.0; + list.forEach(function (elem) { + sum += f(elem); + }) + return sum; +} +WidgetStack.prototype.calculateDimensions = function () { + var totalWidth = 0.0, maxWidth = 0.0; + var totalHeight = 0.0, maxHeight = 0.0; + this.widgets.forEach(function (widget) { + totalWidth += widget.getWidth() + this.padding.x; + maxWidth = Math.max(maxWidth, widget.getWidth()); -var Icon = UI.Icon = function (properties) { + totalHeight += widget.getHeight() + this.padding.y; + maxHeight = Math.max(maxHeight, widget.getHeight()); + }, this); + + this.dimensions = { + x: this.border.x * 2 + Math.max(totalWidth * this.layoutDir.x - this.padding.x, maxWidth), + y: this.border.y * 2 + Math.max(totalHeight * this.layoutDir.y - this.padding.y, maxHeight) + }; +} +WidgetStack.prototype.getWidth = function () { + if (!this.dimensions) + this.calculateDimensions(); + return this.dimensions.x; +} +WidgetStack.prototype.getHeight = function () { + if (!this.dimensions) + this.calculateDimensions(); + return this.dimensions.y; +} +WidgetStack.prototype.applyLayout = function () { + var x = this.position.x + this.border.x; + var y = this.position.y + this.border.y; + + this.widgets.forEach(function (widget) { + widget.setPosition(x, y); + x += (widget.getWidth() + this.padding.x) * this.layoutDir.x; + y += (widget.getHeight() + this.padding.y) * this.layoutDir.y; + widget._parentVisible = this.isVisible(); + }, this); +} +WidgetStack.prototype.updateOverlays = function () { + if (this.backgroundOverlay) { + this.backgroundOverlay.update({ + width: this.getWidth(), + height: this.getHeight(), + x: this.position.x, + y: this.position.y, + visible: this.isVisible() + }); + } +} + + +/// GUI Textured Rect +var Image = UI.Image = function (properties) { Widget.prototype.constructor.call(this); this.visible = properties.visible != undefined ? properties.visible : this.visible; this.width = properties.width || 1.0; this.height = properties.height || 1.0; - var iconProperties = { + var imageProperties = { 'color': properties.color || COLOR_GRAY, 'alpha': properties.alpha || 1.0, 'imageURL': properties.imageURL, @@ -367,42 +415,58 @@ var Icon = UI.Icon = function (properties) { 'y': this.position ? this.position.y : 0.0, 'visible': this.visible } - this.iconOverlay = makeOverlay("image", iconProperties); + this.imageOverlay = makeOverlay("image", imageProperties); } -Icon.prototype = new Widget(); -Icon.prototype.constructor = Icon; -Icon.prototype.toString = function () { - return "[UI.Icon " + this.id + " ]"; +Image.prototype = new Widget(); +Image.prototype.constructor = Image; +Image.prototype.toString = function () { + return "[UI.Image " + this.id + " ]"; } -Icon.prototype.getHeight = function () { +Image.prototype.getHeight = function () { return this.height; } -Icon.prototype.getWidth = function () { +Image.prototype.getWidth = function () { return this.width; } -Icon.prototype.hasOverlay = function (overlayId) { - return this.iconOverlay.getId() === overlayId; +Image.prototype.hasOverlay = function (overlayId) { + return this.imageOverlay.getId() === overlayId; } -Icon.prototype.getOverlay = function () { - return this.iconOverlay; +Image.prototype.getOverlay = function () { + return this.imageOverlay; } - -Icon.prototype.destroy = function () { - if (this.iconOverlay) { - this.iconOverlay.destroy(); - this.iconOverlay = null; +Image.prototype.destroy = function () { + if (this.imageOverlay) { + this.imageOverlay.destroy(); + this.imageOverlay = null; } } -Icon.prototype.setColor = function (color) { +Image.prototype.setColor = function (color) { if (arguments.length != 1) { color = rgba.apply(arguments); } - this.iconOverlay.update({ + this.imageOverlay.update({ 'color': color, 'alpha': color.a }); } +Image.prototype.getWidth = function () { + return this.width; +} +Image.prototype.getHeight = function () { + return this.height; +} +Image.prototype.updateOverlays = function () { + this.imageOverlay.update({ + width: this.width, + height: this.height, + x: this.position.x, + y: this.position.y, + visible: this.isVisible() + }); +} + +/// GUI Rect. Internally implemented using a text overlay. var Box = UI.Box = function (properties) { Widget.prototype.constructor.call(this); @@ -485,7 +549,7 @@ Label.prototype.setText = function (text) { }); } -/// Slider element. +/// Slider widget. /// @param properties: /// onValueChanged var Slider = UI.Slider = function (properties) { @@ -624,77 +688,9 @@ Checkbox.prototype.applyLayout = function () { } -Icon.prototype.getWidth = function () { - return this.width; -} -Icon.prototype.getHeight = function () { - return this.height; -} -Icon.prototype.updateOverlays = function () { - this.iconOverlay.update({ - width: this.width, - height: this.height, - x: this.position.x, - y: this.position.y, - visible: this.isVisible() - }); -} -var sumOf = function (list, f) { - var sum = 0.0; - list.forEach(function (elem) { - sum += f(elem); - }) - return sum; -} -WidgetStack.prototype.calculateDimensions = function () { - var totalWidth = 0.0, maxWidth = 0.0; - var totalHeight = 0.0, maxHeight = 0.0; - this.widgets.forEach(function (widget) { - totalWidth += widget.getWidth() + this.padding.x; - maxWidth = Math.max(maxWidth, widget.getWidth()); - totalHeight += widget.getHeight() + this.padding.y; - maxHeight = Math.max(maxHeight, widget.getHeight()); - }, this); - this.dimensions = { - x: this.border.x * 2 + Math.max(totalWidth * this.layoutDir.x - this.padding.x, maxWidth), - y: this.border.y * 2 + Math.max(totalHeight * this.layoutDir.y - this.padding.y, maxHeight) - }; -} -WidgetStack.prototype.getWidth = function () { - if (!this.dimensions) - this.calculateDimensions(); - return this.dimensions.x; -} -WidgetStack.prototype.getHeight = function () { - if (!this.dimensions) - this.calculateDimensions(); - return this.dimensions.y; -} -WidgetStack.prototype.applyLayout = function () { - var x = this.position.x + this.border.x; - var y = this.position.y + this.border.y; - - this.widgets.forEach(function (widget) { - widget.setPosition(x, y); - x += (widget.getWidth() + this.padding.x) * this.layoutDir.x; - y += (widget.getHeight() + this.padding.y) * this.layoutDir.y; - widget._parentVisible = this.isVisible(); - }, this); -} -WidgetStack.prototype.updateOverlays = function () { - if (this.backgroundOverlay) { - this.backgroundOverlay.update({ - width: this.getWidth(), - height: this.getHeight(), - x: this.position.x, - y: this.position.y, - visible: this.isVisible() - }); - } -} UI.addAttachment = function (target, rel, update) { attachment = { target: target, @@ -902,7 +898,6 @@ ui.logEvent = function (event) { } - // Tries to find a widget with an overlay matching overlay. // Used by getFocusedWidget(), dispatchEvent(), etc var getWidgetWithOverlay = function (overlay) { From fdacb894a5754cb9391461e7689c84c935748352 Mon Sep 17 00:00:00 2001 From: Seiji Emery Date: Wed, 12 Aug 2015 16:28:06 -0700 Subject: [PATCH 52/53] tabs to spaces --- examples/libraries/uiwidgets.js | 1206 +++++++++++++++---------------- 1 file changed, 603 insertions(+), 603 deletions(-) diff --git a/examples/libraries/uiwidgets.js b/examples/libraries/uiwidgets.js index c53bb9ba65..fa898ea7bc 100644 --- a/examples/libraries/uiwidgets.js +++ b/examples/libraries/uiwidgets.js @@ -1,9 +1,9 @@ // -// uiwidgets.js -// examples/libraries +// uiwidgets.js +// examples/libraries // -// Created by Seiji Emery, 8/10/15 -// Copyright 2015 High Fidelity, Inc +// Created by Seiji Emery, 8/10/15 +// Copyright 2015 High Fidelity, Inc // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -17,162 +17,162 @@ // We need a Vec2 impl, with add() and a clone function. If this is not part of hifi, we'll just add it: if (this.Vec2 == undefined) { - var Vec2 = this.Vec2 = function (x, y) { - this.x = x || 0.0; - this.y = y || 0.0; - } - Vec2.sum = function (a, b) { - return new Vec2(a.x + b.x, a.y + b.y); - } - Vec2.clone = function (v) { - return new Vec2(v.x, v.y); - } + var Vec2 = this.Vec2 = function (x, y) { + this.x = x || 0.0; + this.y = y || 0.0; + } + Vec2.sum = function (a, b) { + return new Vec2(a.x + b.x, a.y + b.y); + } + Vec2.clone = function (v) { + return new Vec2(v.x, v.y); + } } else if (this.Vec2.clone == undefined) { - print("Vec2 exists; adding Vec2.clone"); - this.Vec2.clone = function (v) { - return { 'x': v.x || 0.0, 'y': v.y || 0.0 }; - } + print("Vec2 exists; adding Vec2.clone"); + this.Vec2.clone = function (v) { + return { 'x': v.x || 0.0, 'y': v.y || 0.0 }; + } } else { - print("Vec2...?"); + print("Vec2...?"); } })(); var Rect = function (xmin, ymin, xmax, ymax) { - this.x0 = xmin; - this.y0 = ymin; - this.x1 = xmax; - this.y1 = ymax; + this.x0 = xmin; + this.y0 = ymin; + this.x1 = xmax; + this.y1 = ymax; } Rect.prototype.grow = function (pt) { - this.x0 = Math.min(this.x0, pt.x); - this.y0 = Math.min(this.y0, pt.y); - this.x1 = Math.max(this.x1, pt.x); - this.y1 = Math.max(this.y1, pt.y); + this.x0 = Math.min(this.x0, pt.x); + this.y0 = Math.min(this.y0, pt.y); + this.x1 = Math.max(this.x1, pt.x); + this.y1 = Math.max(this.y1, pt.y); } Rect.prototype.getWidth = function () { - return this.x1 - this.x0; + return this.x1 - this.x0; } Rect.prototype.getHeight = function () { - return this.y1 - this.y0; + return this.y1 - this.y0; } Rect.prototype.getTopLeft = function () { - return { 'x': this.x0, 'y': this.y0 }; + return { 'x': this.x0, 'y': this.y0 }; } Rect.prototype.getBtmRight = function () { - return { 'x': this.x1, 'y': this.y1 }; + return { 'x': this.x1, 'y': this.y1 }; } Rect.prototype.getCenter = function () { - return { - 'x': 0.5 * (this.x1 + this.x0), - 'y': 0.5 * (this.y1 + this.y0) - }; + return { + 'x': 0.5 * (this.x1 + this.x0), + 'y': 0.5 * (this.y1 + this.y0) + }; } var __trace = new Array(); var __traceDepth = 0; var assert = function (cond, expr) { - if (!cond) { - var callstack = ""; - var maxRecursion = 10; - caller = arguments.callee.caller; - while (maxRecursion > 0 && caller) { - --maxRecursion; - callstack += ">> " + caller.toString(); - caller = caller.caller; - } - throw new Error("assertion failed: " + expr + " (" + cond + ")" + "\n" + - "Called from: " + callstack + " " + - "Traceback: \n\t" + __trace.join("\n\t")); - } + if (!cond) { + var callstack = ""; + var maxRecursion = 10; + caller = arguments.callee.caller; + while (maxRecursion > 0 && caller) { + --maxRecursion; + callstack += ">> " + caller.toString(); + caller = caller.caller; + } + throw new Error("assertion failed: " + expr + " (" + cond + ")" + "\n" + + "Called from: " + callstack + " " + + "Traceback: \n\t" + __trace.join("\n\t")); + } } var traceEnter = function(fcn) { - var l = __trace.length; - // print("TRACE ENTER: " + (l+1)); - s = ""; - for (var i = 0; i < __traceDepth+1; ++i) - s += "-"; - ++__traceDepth; - __trace.push(s + fcn); - __trace.push(__trace.pop() + ":" + this); - return { - 'exit': function () { - --__traceDepth; - // while (__trace.length != l) - // __trace.pop(); - } - }; + var l = __trace.length; + // print("TRACE ENTER: " + (l+1)); + s = ""; + for (var i = 0; i < __traceDepth+1; ++i) + s += "-"; + ++__traceDepth; + __trace.push(s + fcn); + __trace.push(__trace.pop() + ":" + this); + return { + 'exit': function () { + --__traceDepth; + // while (__trace.length != l) + // __trace.pop(); + } + }; } /// UI namespace var UI = this.UI = {}; var rgb = UI.rgb = function (r, g, b) { - if (typeof(r) == 'string') { - rs = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(r); - if (rs) { - r = parseInt(rs[0], 16); - g = parseInt(rs[1], 16); - b = parseInt(rs[2], 16); - } - } - if (typeof(r) != 'number' || typeof(g) != 'number' || typeof(b) != 'number') { - ui.err("Invalid args to UI.rgb (" + r + ", " + g + ", " + b + ")"); - return null; - } - return { 'red': r, 'green': g, 'blue': b }; + if (typeof(r) == 'string') { + rs = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(r); + if (rs) { + r = parseInt(rs[0], 16); + g = parseInt(rs[1], 16); + b = parseInt(rs[2], 16); + } + } + if (typeof(r) != 'number' || typeof(g) != 'number' || typeof(b) != 'number') { + ui.err("Invalid args to UI.rgb (" + r + ", " + g + ", " + b + ")"); + return null; + } + return { 'red': r, 'green': g, 'blue': b }; } var rgba = UI.rgba = function (r, g, b, a) { - if (typeof(r) == 'string') - return rgb(r); - return { 'red': r || 0, 'green': g || 0, 'blue': b || 0, 'a': a }; + if (typeof(r) == 'string') + return rgb(r); + return { 'red': r || 0, 'green': g || 0, 'blue': b || 0, 'a': a }; } /// Protected UI state var ui = { - defaultVisible: true, - widgetList: new Array(), - attachmentList: new Array() + defaultVisible: true, + widgetList: new Array(), + attachmentList: new Array() }; ui.complain = function (msg) { - print("WARNING (uiwidgets.js): " + msg); + print("WARNING (uiwidgets.js): " + msg); } ui.errorHandler = function (err) { - print(err); + print(err); } ui.assert = function (condition, message) { - if (!condition) { - message = "FAILED ASSERT (uiwidgets.js): " + message || "(" + condition + ")"; - ui.errorHandler(message); - if (typeof(Error) !== 'undefined') - throw new Error(message); - throw message; - } + if (!condition) { + message = "FAILED ASSERT (uiwidgets.js): " + message || "(" + condition + ")"; + ui.errorHandler(message); + if (typeof(Error) !== 'undefined') + throw new Error(message); + throw message; + } } UI.setDefaultVisibility = function (visible) { - ui.defaultVisible = visible; + ui.defaultVisible = visible; } /// Wrapper around the overlays impl function makeOverlay(type, properties) { - var overlay = Overlays.addOverlay(type, properties); - return { - 'update': function (properties) { - var _TRACE = traceEnter.call(this, "Overlay.update"); - Overlays.editOverlay(overlay, properties); - _TRACE.exit(); - }, - 'destroy': function () { - var _TRACE = traceEnter.call(this, "Overlay.destroy"); - Overlays.deleteOverlay(overlay); - _TRACE.exit(); - }, - 'getId': function () { - return overlay; - } - } + var overlay = Overlays.addOverlay(type, properties); + return { + 'update': function (properties) { + var _TRACE = traceEnter.call(this, "Overlay.update"); + Overlays.editOverlay(overlay, properties); + _TRACE.exit(); + }, + 'destroy': function () { + var _TRACE = traceEnter.call(this, "Overlay.destroy"); + Overlays.deleteOverlay(overlay); + _TRACE.exit(); + }, + 'getId': function () { + return overlay; + } + } } var COLOR_WHITE = rgb(255, 255, 255); @@ -184,161 +184,161 @@ var Widget = function () {}; // Shared methods: var __widgetId = 0; Widget.prototype.constructor = function () { - this.position = { 'x': 0.0, 'y': 0.0 }; - this.dimensions = null; + this.position = { 'x': 0.0, 'y': 0.0 }; + this.dimensions = null; - this.visible = ui.defaultVisible; - this.parentVisible = null; + this.visible = ui.defaultVisible; + this.parentVisible = null; - this.actions = {}; - this._dirty = true; - this.parent = null; + this.actions = {}; + this._dirty = true; + this.parent = null; - this.id = __widgetId++; - ui.widgetList.push(this); + this.id = __widgetId++; + ui.widgetList.push(this); } Widget.prototype.setPosition = function (x, y) { - if (arguments.length == 1 && typeof(arguments[0]) == 'object') { - x = arguments[0].x; - y = arguments[0].y; - } - if (typeof(x) != 'number' || typeof(y) != 'number') { - ui.complain("invalid arguments to " + this + ".setPosition: '" + arguments + "' (expected (x, y) or (vec2))"); - } else { - this.position.x = x; - this.position.y = y; - } + if (arguments.length == 1 && typeof(arguments[0]) == 'object') { + x = arguments[0].x; + y = arguments[0].y; + } + if (typeof(x) != 'number' || typeof(y) != 'number') { + ui.complain("invalid arguments to " + this + ".setPosition: '" + arguments + "' (expected (x, y) or (vec2))"); + } else { + this.position.x = x; + this.position.y = y; + } } Widget.prototype.setVisible = function (visible) { - this.visible = visible; - this.parentVisible = null; // set dirty + this.visible = visible; + this.parentVisible = null; // set dirty } Widget.prototype.isVisible = function () { - if (this.parentVisible === null) - this.parentVisible = this.parent ? this.parent.isVisible() : true; - return this.visible && this.parentVisible; + if (this.parentVisible === null) + this.parentVisible = this.parent ? this.parent.isVisible() : true; + return this.visible && this.parentVisible; } // Store lists of actions (multiple callbacks per key) Widget.prototype.addAction = function (action, callback) { - if (!this.actions[action]) - this.actions[action] = [ callback ]; - else - this.actions[action].push(callback); + if (!this.actions[action]) + this.actions[action] = [ callback ]; + else + this.actions[action].push(callback); } Widget.prototype.clearLayout = function () { - this.dimensions = null; - this.parentVisible = null; + this.dimensions = null; + this.parentVisible = null; } // Overridden methods: Widget.prototype.toString = function () { - return "[Widget " + this.id + " ]"; + return "[Widget " + this.id + " ]"; } Widget.prototype.getOverlay = function () { - return null; + return null; } Widget.prototype.getWidth = function () { - return 0; + return 0; } Widget.prototype.getHeight = function () { - return 0; + return 0; } Widget.prototype.hasOverlay = function () { - return false; + return false; } Widget.prototype.applyLayout = function () {}; Widget.prototype.updateOverlays = function () {}; /// Implements a simple auto-layouted container of methods. -/// @param properties -/// dir: [string] -/// layout direction. -/// Can be one of [ '+x', '+y', '-x', '-y' ] for 2d directions. -/// border: { x: _, y: _ } -/// Adds spacing to the widget on all sides (aka. margin). Defaults to 0. -/// padding: { x: _, y: _ } -/// Padding in between each widget. Only one axis is used (the layout direction). -/// visible: true | false -/// Acts as both a widget (logical) property and is used for overlays. -/// Hiding this will hide all child widgets (non-destructively). -/// Do not access this directly -- use setVisible(value) and isVisible() instead. -/// background: [object] -/// Properties to use for the background overlay (if defined). +/// @param properties +/// dir: [string] +/// layout direction. +/// Can be one of [ '+x', '+y', '-x', '-y' ] for 2d directions. +/// border: { x: _, y: _ } +/// Adds spacing to the widget on all sides (aka. margin). Defaults to 0. +/// padding: { x: _, y: _ } +/// Padding in between each widget. Only one axis is used (the layout direction). +/// visible: true | false +/// Acts as both a widget (logical) property and is used for overlays. +/// Hiding this will hide all child widgets (non-destructively). +/// Do not access this directly -- use setVisible(value) and isVisible() instead. +/// background: [object] +/// Properties to use for the background overlay (if defined). /// var WidgetStack = UI.WidgetStack = function (properties) { - var _TRACE = traceEnter.call(this, "WidgetStack.constructor()"); - Widget.prototype.constructor.call(this); - assert(ui.widgetList[ui.widgetList.length-1] === this, "ui.widgetList.back() == this"); + var _TRACE = traceEnter.call(this, "WidgetStack.constructor()"); + Widget.prototype.constructor.call(this); + assert(ui.widgetList[ui.widgetList.length-1] === this, "ui.widgetList.back() == this"); - properties = properties || {}; - properties['dir'] = properties['dir'] || '+y'; + properties = properties || {}; + properties['dir'] = properties['dir'] || '+y'; - var dir = undefined; - switch(properties['dir']) { - case '+y': dir = { 'x': 0.0, 'y': 1.0 }; break; - case '-y': dir = { 'x': 0.0, 'y': -1.0 }; break; - case '+x': dir = { 'x': 1.0, 'y': 0.0 }; break; - case '-x': dir = { 'x': -1.0, 'y': 0.0 }; break; - default: ui.complain("Unrecognized UI.WidgetStack property 'dir': \"" + dir + "\""); - } - dir = dir || { 'x': 1.0, 'y': 0.0 }; + var dir = undefined; + switch(properties['dir']) { + case '+y': dir = { 'x': 0.0, 'y': 1.0 }; break; + case '-y': dir = { 'x': 0.0, 'y': -1.0 }; break; + case '+x': dir = { 'x': 1.0, 'y': 0.0 }; break; + case '-x': dir = { 'x': -1.0, 'y': 0.0 }; break; + default: ui.complain("Unrecognized UI.WidgetStack property 'dir': \"" + dir + "\""); + } + dir = dir || { 'x': 1.0, 'y': 0.0 }; - this.layoutDir = dir; - this.border = properties.border || { 'x': 0.0, 'y': 0.0 }; - this.padding = properties.padding || { 'x': 0.0, 'y': 0.0 }; - this.visible = properties.visible != undefined ? properties.visible : this.visible; + this.layoutDir = dir; + this.border = properties.border || { 'x': 0.0, 'y': 0.0 }; + this.padding = properties.padding || { 'x': 0.0, 'y': 0.0 }; + this.visible = properties.visible != undefined ? properties.visible : this.visible; - if (properties.background) { - var background = properties.background; - background.x = this.position ? this.position.x : 0; - background.y = this.position ? this.position.y : 0; - background.width = background.width || 100.0; - background.height = background.height || 100.0; - background.backgroundColor = background.backgroundColor || COLOR_GRAY; - background.backgroundAlpha = background.backgroundAlpha || 0.5; - background.textColor = background.textColor || COLOR_WHITE; - background.alpha = background.alpha || 1.0; - background.visible = this.visible; - this.backgroundOverlay = makeOverlay("text", background); - } else { - this.backgroundOverlay = null; - } + if (properties.background) { + var background = properties.background; + background.x = this.position ? this.position.x : 0; + background.y = this.position ? this.position.y : 0; + background.width = background.width || 100.0; + background.height = background.height || 100.0; + background.backgroundColor = background.backgroundColor || COLOR_GRAY; + background.backgroundAlpha = background.backgroundAlpha || 0.5; + background.textColor = background.textColor || COLOR_WHITE; + background.alpha = background.alpha || 1.0; + background.visible = this.visible; + this.backgroundOverlay = makeOverlay("text", background); + } else { + this.backgroundOverlay = null; + } - this.widgets = new Array(); + this.widgets = new Array(); - _TRACE.exit(); + _TRACE.exit(); } WidgetStack.prototype = new Widget(); WidgetStack.prototype.constructor = WidgetStack; WidgetStack.prototype.toString = function () { - return "[WidgetStack " + this.id + " ]"; + return "[WidgetStack " + this.id + " ]"; } WidgetStack.prototype.add = function (widget) { - this.widgets.push(widget); - widget.parent = this; - return widget; + this.widgets.push(widget); + widget.parent = this; + return widget; } WidgetStack.prototype.hasOverlay = function (overlayId) { - return this.backgroundOverlay && this.backgroundOverlay.getId() === overlayId; + return this.backgroundOverlay && this.backgroundOverlay.getId() === overlayId; } WidgetStack.prototype.getOverlay = function () { - return this.backgroundOverlay; + return this.backgroundOverlay; } WidgetStack.prototype.destroy = function () { - if (this.backgroundOverlay) { - this.backgroundOverlay.destroy(); - this.backgroundOverlay = null; - } + if (this.backgroundOverlay) { + this.backgroundOverlay.destroy(); + this.backgroundOverlay = null; + } } WidgetStack.prototype.setColor = function (color) { - if (arguments.length != 1) { - color = rgba.apply(arguments); - } - this.backgroundOverlay.update({ - 'color': color, - 'alpha': color.a - }); + if (arguments.length != 1) { + color = rgba.apply(arguments); + } + this.backgroundOverlay.update({ + 'color': color, + 'alpha': color.a + }); } var sumOf = function (list, f) { var sum = 0.0; @@ -399,55 +399,55 @@ WidgetStack.prototype.updateOverlays = function () { /// GUI Textured Rect var Image = UI.Image = function (properties) { - Widget.prototype.constructor.call(this); + Widget.prototype.constructor.call(this); - this.visible = properties.visible != undefined ? properties.visible : this.visible; - this.width = properties.width || 1.0; - this.height = properties.height || 1.0; + this.visible = properties.visible != undefined ? properties.visible : this.visible; + this.width = properties.width || 1.0; + this.height = properties.height || 1.0; - var imageProperties = { - 'color': properties.color || COLOR_GRAY, - 'alpha': properties.alpha || 1.0, - 'imageURL': properties.imageURL, - 'width': this.width, - 'height': this.height, - 'x': this.position ? this.position.x : 0.0, - 'y': this.position ? this.position.y : 0.0, - 'visible': this.visible - } - this.imageOverlay = makeOverlay("image", imageProperties); + var imageProperties = { + 'color': properties.color || COLOR_GRAY, + 'alpha': properties.alpha || 1.0, + 'imageURL': properties.imageURL, + 'width': this.width, + 'height': this.height, + 'x': this.position ? this.position.x : 0.0, + 'y': this.position ? this.position.y : 0.0, + 'visible': this.visible + } + this.imageOverlay = makeOverlay("image", imageProperties); } Image.prototype = new Widget(); Image.prototype.constructor = Image; Image.prototype.toString = function () { - return "[UI.Image " + this.id + " ]"; + return "[UI.Image " + this.id + " ]"; } Image.prototype.getHeight = function () { - return this.height; + return this.height; } Image.prototype.getWidth = function () { - return this.width; + return this.width; } Image.prototype.hasOverlay = function (overlayId) { - return this.imageOverlay.getId() === overlayId; + return this.imageOverlay.getId() === overlayId; } Image.prototype.getOverlay = function () { - return this.imageOverlay; + return this.imageOverlay; } Image.prototype.destroy = function () { - if (this.imageOverlay) { - this.imageOverlay.destroy(); - this.imageOverlay = null; - } + if (this.imageOverlay) { + this.imageOverlay.destroy(); + this.imageOverlay = null; + } } Image.prototype.setColor = function (color) { - if (arguments.length != 1) { - color = rgba.apply(arguments); - } - this.imageOverlay.update({ - 'color': color, - 'alpha': color.a - }); + if (arguments.length != 1) { + color = rgba.apply(arguments); + } + this.imageOverlay.update({ + 'color': color, + 'alpha': color.a + }); } Image.prototype.getWidth = function () { return this.width; @@ -468,223 +468,223 @@ Image.prototype.updateOverlays = function () { /// GUI Rect. Internally implemented using a text overlay. var Box = UI.Box = function (properties) { - Widget.prototype.constructor.call(this); + Widget.prototype.constructor.call(this); - properties = properties || {}; - properties.width = properties.width || 10; - properties.height = properties.height || 10; - properties.visible = properties.visible !== undefined ? properties.visible : this.visible; - properties.x = this.position.x; - properties.y = this.position.y; + properties = properties || {}; + properties.width = properties.width || 10; + properties.height = properties.height || 10; + properties.visible = properties.visible !== undefined ? properties.visible : this.visible; + properties.x = this.position.x; + properties.y = this.position.y; - this.width = properties.width; - this.height = properties.height; - this.visible = properties.visible; + this.width = properties.width; + this.height = properties.height; + this.visible = properties.visible; - this.overlay = makeOverlay("text", properties); + this.overlay = makeOverlay("text", properties); }; Box.prototype = new Widget(); Box.prototype.constructor = Box; Box.prototype.toString = function () { - return "[UI.Box " + this.id + " ]"; + return "[UI.Box " + this.id + " ]"; } Box.prototype.getWidth = function () { - return this.width; + return this.width; } Box.prototype.getHeight = function () { - return this.height; + return this.height; } Box.prototype.destroy = function () { - if (this.overlay) { - this.overlay.destroy(); - this.overlay = null; - } + if (this.overlay) { + this.overlay.destroy(); + this.overlay = null; + } } Box.prototype.hasOverlay = function (overlayId) { - return this.overlay && this.overlay.getId() === overlayId; + return this.overlay && this.overlay.getId() === overlayId; } Box.prototype.getOverlay = function () { - return this.overlay; + return this.overlay; } Box.prototype.updateOverlays = function () { - this.overlay.update({ - x: this.position.x, - y: this.position.y, - width: this.width, - height: this.height, - visible: this.isVisible() - }); + this.overlay.update({ + x: this.position.x, + y: this.position.y, + width: this.width, + height: this.height, + visible: this.isVisible() + }); } var Label = UI.Label = function (properties) { - properties = properties || {}; - if (properties.text === undefined || properties.text === null) - properties.text = "< bad UI.Label call (text) >"; + properties = properties || {}; + if (properties.text === undefined || properties.text === null) + properties.text = "< bad UI.Label call (text) >"; - if (!properties.width) { - ui.complain("UI.Label constructor expected width property, not " + properties.width); - this.text = "< bad UI.Label call (width) >"; - properties.width = 220; - } - if (!properties.height) { - ui.complain("UI.Label constructor expected height property, not " + properties.height); - this.text = "< bad UI.Label call (height) >"; - properties.height = 20; - } - properties.color = properties.color || COLOR_WHITE; - properties.alpha = properties.alpha || properties.color.a || 1.0; - properties.backgroundAlpha = properties.backgroundAlpha || 0.0; + if (!properties.width) { + ui.complain("UI.Label constructor expected width property, not " + properties.width); + this.text = "< bad UI.Label call (width) >"; + properties.width = 220; + } + if (!properties.height) { + ui.complain("UI.Label constructor expected height property, not " + properties.height); + this.text = "< bad UI.Label call (height) >"; + properties.height = 20; + } + properties.color = properties.color || COLOR_WHITE; + properties.alpha = properties.alpha || properties.color.a || 1.0; + properties.backgroundAlpha = properties.backgroundAlpha || 0.0; - Box.prototype.constructor.call(this, properties); + Box.prototype.constructor.call(this, properties); }; Label.prototype = new Box(); Label.prototype.constructor = Label; Label.prototype.toString = function () { - return "[UI.Label " + this.id + " ]"; + return "[UI.Label " + this.id + " ]"; } Label.prototype.setText = function (text) { - this.text = text; - this.overlay.update({ - text: text - }); + this.text = text; + this.overlay.update({ + text: text + }); } /// Slider widget. /// @param properties: -/// onValueChanged +/// onValueChanged var Slider = UI.Slider = function (properties) { - Box.prototype.constructor.call(this, properties); - // print("CONSTRUCTING SLIDER") - this.value = properties.value || 0.0; - this.maxValue = properties.maxValue || 1.0; - this.minValue = properties.minValue || 0.0; - this.padding = properties.padding || { - x: 4, y: 4 - } - this.onValueChanged = properties.onValueChanged || function () {}; + Box.prototype.constructor.call(this, properties); + // print("CONSTRUCTING SLIDER") + this.value = properties.value || 0.0; + this.maxValue = properties.maxValue || 1.0; + this.minValue = properties.minValue || 0.0; + this.padding = properties.padding || { + x: 4, y: 4 + } + this.onValueChanged = properties.onValueChanged || function () {}; - this.slider = new Box(properties.slider); - this.slider.parent = this; + this.slider = new Box(properties.slider); + this.slider.parent = this; - var updateSliderPos = function (event, widget) { - var rx = Math.max(event.x * 1.0 - widget.position.x - widget.slider.width * 0.5, 0.0); - var width = Math.max(widget.width - widget.slider.width - widget.padding.x * 2.0, 0.0); - var v = Math.min(rx, width) / (width || 1); + var updateSliderPos = function (event, widget) { + var rx = Math.max(event.x * 1.0 - widget.position.x - widget.slider.width * 0.5, 0.0); + var width = Math.max(widget.width - widget.slider.width - widget.padding.x * 2.0, 0.0); + var v = Math.min(rx, width) / (width || 1); - widget.value = widget.minValue + ( - widget.maxValue - widget.minValue) * v; - widget.onValueChanged(widget.value); - UI.updateLayout(); - } + widget.value = widget.minValue + ( + widget.maxValue - widget.minValue) * v; + widget.onValueChanged(widget.value); + UI.updateLayout(); + } - var widget = this; - this.addAction('onMouseDown', function (event) { - sliderRel.x = sliderRel.y = 0.0; - // sliderRel.x = widget.slider.width * 0.5; - // sliderRel.y = widget.slider.height * 0.5; - updateSliderPos(event, widget); + var widget = this; + this.addAction('onMouseDown', function (event) { + sliderRel.x = sliderRel.y = 0.0; + // sliderRel.x = widget.slider.width * 0.5; + // sliderRel.y = widget.slider.height * 0.5; + updateSliderPos(event, widget); - // hack - ui.clickedWidget = ui.draggedWidget = widget.slider; - }); + // hack + ui.clickedWidget = ui.draggedWidget = widget.slider; + }); - var sliderRel = {}; - this.slider.addAction('onMouseDown', function (event) { - sliderRel.x = widget.slider.position.x - event.x; - sliderRel.y = widget.slider.position.y - event.y; - event.x += sliderRel.x; - event.y += sliderRel.y; - updateSliderPos(event, widget); - }); - this.slider.addAction('onDragBegin', function (event) { - event.x += sliderRel.x; - event.y += sliderRel.y; - updateSliderPos(event, widget); - }) - this.slider.addAction('onDragUpdate', function (event) { - event.x += sliderRel.x; - event.y += sliderRel.y; - updateSliderPos(event, widget); - }) + var sliderRel = {}; + this.slider.addAction('onMouseDown', function (event) { + sliderRel.x = widget.slider.position.x - event.x; + sliderRel.y = widget.slider.position.y - event.y; + event.x += sliderRel.x; + event.y += sliderRel.y; + updateSliderPos(event, widget); + }); + this.slider.addAction('onDragBegin', function (event) { + event.x += sliderRel.x; + event.y += sliderRel.y; + updateSliderPos(event, widget); + }) + this.slider.addAction('onDragUpdate', function (event) { + event.x += sliderRel.x; + event.y += sliderRel.y; + updateSliderPos(event, widget); + }) }; Slider.prototype = new Box(); Slider.prototype.constructor = Slider; Slider.prototype.toString = function () { - return "[UI.Slider " + this.id + " ]"; + return "[UI.Slider " + this.id + " ]"; } Slider.prototype.applyLayout = function () { - if (!this.slider) { - ui.complain("Slider.applyLayout on " + this + " failed"); - return; - } - var val = (this.value - this.minValue) / (this.maxValue - this.minValue); - this.slider.position.x = this.position.x + this.padding.x + (this.width - this.slider.width - this.padding.x * 2.0) * val; - this.slider.position.y = this.position.y + /*this.padding.y +*/ (this.height - this.slider.height) * 0.5; + if (!this.slider) { + ui.complain("Slider.applyLayout on " + this + " failed"); + return; + } + var val = (this.value - this.minValue) / (this.maxValue - this.minValue); + this.slider.position.x = this.position.x + this.padding.x + (this.width - this.slider.width - this.padding.x * 2.0) * val; + this.slider.position.y = this.position.y + /*this.padding.y +*/ (this.height - this.slider.height) * 0.5; } Slider.prototype.getValue = function () { - return this.value; + return this.value; } Slider.prototype.setValue = function (value) { - this.value = value; - this.onValueChanged(value); - UI.updateLayout(); + this.value = value; + this.onValueChanged(value); + UI.updateLayout(); } var Checkbox = UI.Checkbox = function (properties) { - Box.prototype.constructor.call(this, properties); + Box.prototype.constructor.call(this, properties); - this.checked = properties.checked !== undefined ? properties.checked : true; - this.padding = properties.padding || { x: 4, y: 4 }; + this.checked = properties.checked !== undefined ? properties.checked : true; + this.padding = properties.padding || { x: 4, y: 4 }; - properties.checkMark = properties.checkMark || {}; + properties.checkMark = properties.checkMark || {}; - // Keep square - var r = Math.min(this.width - this.padding.x * 2.0, this.height - this.padding.y * 2.0); - properties.checkMark.width = properties.checkMark.height = r; - properties.checkMark.visible = false; - properties.checkMark.backgroundColor = properties.checkMark.backgroundColor || - properties.checkMark.color || rgb(77, 185, 77); - properties.checkMark.backgroundAlpha = properties.checkMark.backgroundAlpha || - properties.checkMark.alpha || 1.0; - this.checkMark = new Box(properties.checkMark); - this.checkMark.setVisible(this.checked); - this.checkMark.setPosition( - this.position.x + (this.width - this.checkMark.width) * 0.5, - this.position.y + (this.height - this.checkMark.height) * 0.5 - ); + // Keep square + var r = Math.min(this.width - this.padding.x * 2.0, this.height - this.padding.y * 2.0); + properties.checkMark.width = properties.checkMark.height = r; + properties.checkMark.visible = false; + properties.checkMark.backgroundColor = properties.checkMark.backgroundColor || + properties.checkMark.color || rgb(77, 185, 77); + properties.checkMark.backgroundAlpha = properties.checkMark.backgroundAlpha || + properties.checkMark.alpha || 1.0; + this.checkMark = new Box(properties.checkMark); + this.checkMark.setVisible(this.checked); + this.checkMark.setPosition( + this.position.x + (this.width - this.checkMark.width) * 0.5, + this.position.y + (this.height - this.checkMark.height) * 0.5 + ); - this.onValueChanged = properties.onValueChanged || function () {}; + this.onValueChanged = properties.onValueChanged || function () {}; - var widget = this; - this.addAction('onClick', function (event) { - widget.setChecked(!widget.isChecked()); - }); - this.checkMark.addAction('onClick', function (event) { - widget.setChecked(!widget.isChecked()); - }); + var widget = this; + this.addAction('onClick', function (event) { + widget.setChecked(!widget.isChecked()); + }); + this.checkMark.addAction('onClick', function (event) { + widget.setChecked(!widget.isChecked()); + }); }; Checkbox.prototype = new Box(); Checkbox.prototype.constructor = Checkbox; Checkbox.prototype.toString = function () { - return "[UI.Checkbox " + this.id + "]"; + return "[UI.Checkbox " + this.id + "]"; } Checkbox.prototype.isChecked = function () { - return this.checked; + return this.checked; } Checkbox.prototype.setChecked = function (value) { - this.checked = value; - this.checkMark.setVisible(this.checked); + this.checked = value; + this.checkMark.setVisible(this.checked); - this.onValueChanged(value); - UI.updateLayout(); + this.onValueChanged(value); + UI.updateLayout(); } Checkbox.prototype.applyLayout = function () { - this.checkMark && this.checkMark.setPosition( - this.position.x + (this.width - this.checkMark.width) * 0.5, - this.position.y + (this.height - this.checkMark.height) * 0.5 - ); + this.checkMark && this.checkMark.setPosition( + this.position.x + (this.width - this.checkMark.width) * 0.5, + this.position.y + (this.height - this.checkMark.height) * 0.5 + ); } @@ -692,80 +692,80 @@ Checkbox.prototype.applyLayout = function () { UI.addAttachment = function (target, rel, update) { - attachment = { - target: target, - rel: rel, - applyLayout: update - }; - ui.attachmentList.push(attachment); - return attachment; + attachment = { + target: target, + rel: rel, + applyLayout: update + }; + ui.attachmentList.push(attachment); + return attachment; } UI.updateLayout = function () { - if (ui.visible) - ui.updateDebugUI(); + if (ui.visible) + ui.updateDebugUI(); - // Recalc dimensions - ui.widgetList.forEach(function (widget) { - widget.clearLayout(); - }); + // Recalc dimensions + ui.widgetList.forEach(function (widget) { + widget.clearLayout(); + }); - function insertAndPush (list, index, elem) { - if (list[index]) - list[index].push(elem); - else - list[index] = [ elem ]; - } + function insertAndPush (list, index, elem) { + if (list[index]) + list[index].push(elem); + else + list[index] = [ elem ]; + } - // Generate attachment lookup - var attachmentDeps = {}; - ui.attachmentList.forEach(function(attachment) { - insertAndPush(attachmentDeps, attachment.target.id, { - dep: attachment.rel, - eval: attachment.applyLayout - }); - }); - updated = {}; + // Generate attachment lookup + var attachmentDeps = {}; + ui.attachmentList.forEach(function(attachment) { + insertAndPush(attachmentDeps, attachment.target.id, { + dep: attachment.rel, + eval: attachment.applyLayout + }); + }); + updated = {}; - // Walk the widget list and relayout everything - function recalcLayout (widget) { - // Short circuit if we've already updated - if (updated[widget.id]) - return; + // Walk the widget list and relayout everything + function recalcLayout (widget) { + // Short circuit if we've already updated + if (updated[widget.id]) + return; - // Walk up the tree + update top level first - if (widget.parent) - recalcLayout(widget.parent); + // Walk up the tree + update top level first + if (widget.parent) + recalcLayout(widget.parent); - // Resolve and apply attachment dependencies - if (attachmentDeps[widget.id]) { - attachmentDeps[widget.id].forEach(function (attachment) { - recalcLayout(attachment.dep); - attachment.eval(widget, attachment.dep); - }); - } + // Resolve and apply attachment dependencies + if (attachmentDeps[widget.id]) { + attachmentDeps[widget.id].forEach(function (attachment) { + recalcLayout(attachment.dep); + attachment.eval(widget, attachment.dep); + }); + } - widget.applyLayout(); - updated[widget.id] = true; - } - ui.widgetList.forEach(recalcLayout); + widget.applyLayout(); + updated[widget.id] = true; + } + ui.widgetList.forEach(recalcLayout); - ui.widgetList.forEach(function (widget) { - widget.updateOverlays(); - }); + ui.widgetList.forEach(function (widget) { + widget.updateOverlays(); + }); } UI.setDefaultVisibility = function(visibility) { - ui.defaultVisible = visibility; + ui.defaultVisible = visibility; }; function dispatchEvent(actions, widget, event) { - var _TRACE = traceEnter.call(this, "UI.dispatchEvent()"); - actions.forEach(function(action) { - action.call(widget, event); - }); - _TRACE.exit(); + var _TRACE = traceEnter.call(this, "UI.dispatchEvent()"); + actions.forEach(function(action) { + action.call(widget, event); + }); + _TRACE.exit(); } // Focus state @@ -777,23 +777,23 @@ ui.draggedWidget = null; var statusPos = new Vec2(15, 20); var statusDim = new Vec2(500, 20); function makeStatusWidget(defaultText, alpha) { - var label = new Box({ - text: defaultText, - width: statusDim.x, - height: statusDim.y, - color: COLOR_WHITE, - alpha: alpha || 0.98, - backgroundAlpha: 0.0, - visible: ui.debug.visible - }); - label.updateText = function (text) { - label.getOverlay().update({ - text: text - }); - } - label.setPosition(statusPos.x, statusPos.y); - statusPos.y += statusDim.y; - return label; + var label = new Box({ + text: defaultText, + width: statusDim.x, + height: statusDim.y, + color: COLOR_WHITE, + alpha: alpha || 0.98, + backgroundAlpha: 0.0, + visible: ui.debug.visible + }); + label.updateText = function (text) { + label.getOverlay().update({ + text: text + }); + } + label.setPosition(statusPos.x, statusPos.y); + statusPos.y += statusDim.y; + return label; } ui.debug = {}; @@ -802,28 +802,28 @@ ui.focusStatus = makeStatusWidget(""); ui.eventStatus = makeStatusWidget("", 0.85); UI.debug = { - eventList: { - position: { x: 20, y: 20 }, width: 1, height: 1 - }, - widgetList: { - position: { x: 500, y: 20 }, width: 1, height: 1 - }, - setVisible: function (visible) { - if (ui.debug.visible != visible) { - ui.focusStatus.setVisible(visible); - ui.eventStatus.setVisible(visible); - if (visible) { - ui.debug.showWidgetList(UI.debug.widgetList.position.x, UI.debug.widgetList.position.y); - } else { - ui.debug.hideWidgetList(); - } - UI.updateLayout(); - } - ui.debug.visible = visible; - }, - isVisible: function () { - return ui.debug.visible; - } + eventList: { + position: { x: 20, y: 20 }, width: 1, height: 1 + }, + widgetList: { + position: { x: 500, y: 20 }, width: 1, height: 1 + }, + setVisible: function (visible) { + if (ui.debug.visible != visible) { + ui.focusStatus.setVisible(visible); + ui.eventStatus.setVisible(visible); + if (visible) { + ui.debug.showWidgetList(UI.debug.widgetList.position.x, UI.debug.widgetList.position.y); + } else { + ui.debug.hideWidgetList(); + } + UI.updateLayout(); + } + ui.debug.visible = visible; + }, + isVisible: function () { + return ui.debug.visible; + } } // Add debug list of all widgets + attachments @@ -834,188 +834,188 @@ var attachmentList = makeStatusWidget("", 0.85); var lineHeight = 20; ui.debug.relayout = function () { - var x = UI.debug.widgetList.position.x, y = UI.debug.widgetList.position.y; + var x = UI.debug.widgetList.position.x, y = UI.debug.widgetList.position.y; - widgetListHeader.setPosition(x, y); y += lineHeight; - widgetList.updateText(ui.widgetList.map(function (widget) { - return "" + widget + " actions: " + (Object.keys(widget.actions).join(", ") || "none"); - }).join("\n") || "no widgets"); - widgetList.setPosition(x, y); - y += lineHeight * (ui.widgetList.length || 1); + widgetListHeader.setPosition(x, y); y += lineHeight; + widgetList.updateText(ui.widgetList.map(function (widget) { + return "" + widget + " actions: " + (Object.keys(widget.actions).join(", ") || "none"); + }).join("\n") || "no widgets"); + widgetList.setPosition(x, y); + y += lineHeight * (ui.widgetList.length || 1); - attachmentListHeader.setPosition(x, y); y += lineHeight; - attachmentList.updateText(ui.attachmentList.map(function (attachment) { - return "[attachment target: " + attachment.target + ", to: " + attachment.rel + "]"; - }).join('\n') || "no attachments"); - attachmentList.setPosition(x, y); - y += lineHeight * (ui.widgetList.length || 1); - // UI.updateLayout(); + attachmentListHeader.setPosition(x, y); y += lineHeight; + attachmentList.updateText(ui.attachmentList.map(function (attachment) { + return "[attachment target: " + attachment.target + ", to: " + attachment.rel + "]"; + }).join('\n') || "no attachments"); + attachmentList.setPosition(x, y); + y += lineHeight * (ui.widgetList.length || 1); + // UI.updateLayout(); } var defaultX = 500; var defaultY = 20; ui.debug.showWidgetList = function (x, y) { - widgetListHeader.setVisible(true); - widgetList.setVisible(true); - attachmentListHeader.setVisible(true); - attachmentList.setVisible(true); - ui.debug.relayout(x || defaultX, y || defaultY); + widgetListHeader.setVisible(true); + widgetList.setVisible(true); + attachmentListHeader.setVisible(true); + attachmentList.setVisible(true); + ui.debug.relayout(x || defaultX, y || defaultY); } ui.debug.hideWidgetList = function () { - widgetListHeader.setVisible(false); - widgetList.setVisible(false); - attachmentListHeader.setVisible(false); - attachmentList.setVisible(false); + widgetListHeader.setVisible(false); + widgetList.setVisible(false); + attachmentListHeader.setVisible(false); + attachmentList.setVisible(false); } ui.eventStatus.lastPos = { - x: ui.eventStatus.position.x, - y: ui.eventStatus.position.y + x: ui.eventStatus.position.x, + y: ui.eventStatus.position.y }; ui.updateDebugUI = function () { - ui.debug.relayout(); + ui.debug.relayout(); - var dx = ui.eventStatus.position.x - ui.eventStatus.lastPos.x; - var dy = ui.eventStatus.position.y - ui.eventStatus.lastPos.y; + var dx = ui.eventStatus.position.x - ui.eventStatus.lastPos.x; + var dy = ui.eventStatus.position.y - ui.eventStatus.lastPos.y; - ui.focusStatus.position.x += dx; - ui.focusStatus.position.y += dy; - ui.eventStatus.position.x += dx; - ui.eventStatus.position.y += dy; + ui.focusStatus.position.x += dx; + ui.focusStatus.position.y += dy; + ui.eventStatus.position.x += dx; + ui.eventStatus.position.y += dy; - ui.eventStatus.lastPos.x = ui.eventStatus.position.x; - ui.eventStatus.lastPos.y = ui.eventStatus.position.y; + ui.eventStatus.lastPos.x = ui.eventStatus.position.x; + ui.eventStatus.lastPos.y = ui.eventStatus.position.y; } var eventList = []; var maxEvents = 8; ui.logEvent = function (event) { - eventList.push(event); - if (eventList.length >= maxEvents) - eventList.shift(); - ui.eventStatus.updateText(eventList.join('\n')); + eventList.push(event); + if (eventList.length >= maxEvents) + eventList.shift(); + ui.eventStatus.updateText(eventList.join('\n')); } // Tries to find a widget with an overlay matching overlay. // Used by getFocusedWidget(), dispatchEvent(), etc var getWidgetWithOverlay = function (overlay) { - var foundWidget = null; - ui.widgetList.forEach(function(widget) { - if (widget.hasOverlay(overlay)) { - foundWidget = widget; - return; - } - }); + var foundWidget = null; + ui.widgetList.forEach(function(widget) { + if (widget.hasOverlay(overlay)) { + foundWidget = widget; + return; + } + }); - ui.focusStatus.updateText("Widget focus: " + foundWidget); - return foundWidget; + ui.focusStatus.updateText("Widget focus: " + foundWidget); + return foundWidget; } var getFocusedWidget = function (event) { - return getWidgetWithOverlay(Overlays.getOverlayAtPoint({ 'x': event.x, 'y': event.y })); + return getWidgetWithOverlay(Overlays.getOverlayAtPoint({ 'x': event.x, 'y': event.y })); } var dispatchEvent = function (action, event, widget) { - function dispatchActions (actions) { - actions.forEach(function(action) { - action(event, widget); - }); - } + function dispatchActions (actions) { + actions.forEach(function(action) { + action(event, widget); + }); + } - if (widget.actions[action]) { - ui.logEvent("Dispatching action '" + action + "'' to " + widget) - dispatchActions(widget.actions[action]); - } else { - for (var parent = widget.parent; parent != null; parent = parent.parent) { - if (parent.actions[action]) { - ui.logEvent("Dispatching action '" + action + "'' to parent widget " + widget); - dispatchActions(parent.actions[action]); - return; - } - } - ui.logEvent("No action '" + action + "' in " + widget); - } + if (widget.actions[action]) { + ui.logEvent("Dispatching action '" + action + "'' to " + widget) + dispatchActions(widget.actions[action]); + } else { + for (var parent = widget.parent; parent != null; parent = parent.parent) { + if (parent.actions[action]) { + ui.logEvent("Dispatching action '" + action + "'' to parent widget " + widget); + dispatchActions(parent.actions[action]); + return; + } + } + ui.logEvent("No action '" + action + "' in " + widget); + } } UI.handleMouseMove = function (event, canStartDrag) { - if (canStartDrag === undefined) - canStartDrag = true; + if (canStartDrag === undefined) + canStartDrag = true; - // print("mouse moved x = " + event.x + ", y = " + event.y); - var focused = getFocusedWidget(event); + // print("mouse moved x = " + event.x + ", y = " + event.y); + var focused = getFocusedWidget(event); - // print("got focus: " + focused); + // print("got focus: " + focused); - if (canStartDrag && !ui.draggedWidget && ui.clickedWidget && ui.clickedWidget.actions['onDragBegin']) { - ui.draggedWidget = ui.clickedWidget; - dispatchEvent('onDragBegin', event, ui.draggedWidget); - } else if (ui.draggedWidget) { - dispatchEvent('onDragUpdate', event, ui.draggedWidget); - } else if (focused != ui.focusedWidget) { - if (ui.focusedWidget) - dispatchEvent('onMouseExit', event, ui.focusedWidget); - if (focused) - dispatchEvent('onMouseOver', event, focused); - ui.focusedWidget = focused; - } + if (canStartDrag && !ui.draggedWidget && ui.clickedWidget && ui.clickedWidget.actions['onDragBegin']) { + ui.draggedWidget = ui.clickedWidget; + dispatchEvent('onDragBegin', event, ui.draggedWidget); + } else if (ui.draggedWidget) { + dispatchEvent('onDragUpdate', event, ui.draggedWidget); + } else if (focused != ui.focusedWidget) { + if (ui.focusedWidget) + dispatchEvent('onMouseExit', event, ui.focusedWidget); + if (focused) + dispatchEvent('onMouseOver', event, focused); + ui.focusedWidget = focused; + } } UI.handleMousePress = function (event) { - print("Mouse clicked"); - UI.handleMouseMove(event); - ui.clickedWidget = ui.focusedWidget; - if (ui.clickedWidget) { - dispatchEvent('onMouseDown', event, ui.clickedWidget); - } + print("Mouse clicked"); + UI.handleMouseMove(event); + ui.clickedWidget = ui.focusedWidget; + if (ui.clickedWidget) { + dispatchEvent('onMouseDown', event, ui.clickedWidget); + } } UI.handleMouseRelease = function (event) { - print("Mouse released"); + print("Mouse released"); - if (ui.draggedWidget) { - dispatchEvent('onDragEnd', event, ui.draggedWidget); - } else { - UI.handleMouseMove(event, false); - if (ui.focusedWidget) { - dispatchEvent('onMouseUp', event, ui.focusedWidget); + if (ui.draggedWidget) { + dispatchEvent('onDragEnd', event, ui.draggedWidget); + } else { + UI.handleMouseMove(event, false); + if (ui.focusedWidget) { + dispatchEvent('onMouseUp', event, ui.focusedWidget); - if (ui.clickedWidget == ui.focusedWidget) { - dispatchEvent('onClick', event, ui.focusedWidget); - } - } - } - ui.clickedWidget = null; - ui.draggedWidget = null; + if (ui.clickedWidget == ui.focusedWidget) { + dispatchEvent('onClick', event, ui.focusedWidget); + } + } + } + ui.clickedWidget = null; + ui.draggedWidget = null; } UI.teardown = function () { - print("Teardown"); - ui.widgetList.forEach(function(widget) { - widget.destroy(); - }); - ui.widgetList = []; - ui.focusedWidget = null; + print("Teardown"); + ui.widgetList.forEach(function(widget) { + widget.destroy(); + }); + ui.widgetList = []; + ui.focusedWidget = null; }; UI.setErrorHandler = function (errorHandler) { - if (typeof(errorHandler) !== 'function') { - ui.complain("UI.setErrorHandler -- invalid argument: \"" + errorHandler + "\""); - } else { - ui.errorHandler = errorHandler; - } + if (typeof(errorHandler) !== 'function') { + ui.complain("UI.setErrorHandler -- invalid argument: \"" + errorHandler + "\""); + } else { + ui.errorHandler = errorHandler; + } } UI.printWidgets = function () { - print("widgetlist.length = " + ui.widgetList.length); - ui.widgetList.forEach(function(widget) { - print(""+widget + " position=(" + widget.position.x + ", " + widget.position.y + ")" + - " parent = " + widget.parent + " visible = " + widget.isVisible() + - " width = " + widget.getWidth() + ", height = " + widget.getHeight() + - " overlay = " + (widget.getOverlay() && widget.getOverlay().getId()) + - (widget.border ? " border = " + widget.border.x + ", " + widget.border.y : "") + - (widget.padding ? " padding = " + widget.padding.x + ", " + widget.padding.y : "")); - }); + print("widgetlist.length = " + ui.widgetList.length); + ui.widgetList.forEach(function(widget) { + print(""+widget + " position=(" + widget.position.x + ", " + widget.position.y + ")" + + " parent = " + widget.parent + " visible = " + widget.isVisible() + + " width = " + widget.getWidth() + ", height = " + widget.getHeight() + + " overlay = " + (widget.getOverlay() && widget.getOverlay().getId()) + + (widget.border ? " border = " + widget.border.x + ", " + widget.border.y : "") + + (widget.padding ? " padding = " + widget.padding.x + ", " + widget.padding.y : "")); + }); } })(); From daae750d68ef23558b8a53ad1edeba87c0239592 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Wed, 12 Aug 2015 16:40:00 -0700 Subject: [PATCH 53/53] No symbolic names for non-entity contants. --- libraries/networking/src/udt/PacketHeaders.cpp | 2 +- libraries/networking/src/udt/PacketHeaders.h | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index db0b5394fa..5562490344 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -69,7 +69,7 @@ PacketVersion versionForPacketType(PacketType::Value packetType) { case EntityData: return VERSION_ENTITIES_POLYLINE; case AvatarData: - return VERSION_AVATAR_NO_LEAN; + return 12; default: return 11; } diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 3f408d1329..1aeadb1af9 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -142,6 +142,5 @@ const PacketVersion VERSION_ENTITIES_HAVE_SIMULATION_OWNER_AND_ACTIONS_OVER_WIRE const PacketVersion VERSION_ENTITIES_NEW_PROTOCOL_LAYER = 35; const PacketVersion VERSION_POLYVOX_TEXTURES = 36; const PacketVersion VERSION_ENTITIES_POLYLINE = 37; -const PacketVersion VERSION_AVATAR_NO_LEAN = 38; #endif // hifi_PacketHeaders_h \ No newline at end of file