mirror of
https://github.com/overte-org/overte.git
synced 2025-07-23 15:24:03 +02:00
Merge branch 'master' of github.com:highfidelity/hifi into run
This commit is contained in:
commit
8cf94547e4
25 changed files with 677 additions and 124 deletions
|
@ -12,10 +12,8 @@ function(JOIN VALUES GLUE OUTPUT)
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
|
|
||||||
if (NOT DEV_BUILD)
|
|
||||||
set(INTERFACE_QML_QRC ${CMAKE_CURRENT_BINARY_DIR}/qml.qrc)
|
set(INTERFACE_QML_QRC ${CMAKE_CURRENT_BINARY_DIR}/qml.qrc)
|
||||||
generate_qrc(OUTPUT ${INTERFACE_QML_QRC} PATH ${CMAKE_CURRENT_SOURCE_DIR}/resources GLOBS *.qml *.qss *.js *.html *.ttf *.gif *.svg *.png *.jpg)
|
generate_qrc(OUTPUT ${INTERFACE_QML_QRC} PATH ${CMAKE_CURRENT_SOURCE_DIR}/resources GLOBS *.qml *.qss *.js *.html *.ttf *.gif *.svg *.png *.jpg)
|
||||||
endif()
|
|
||||||
|
|
||||||
# set a default root dir for each of our optional externals if it was not passed
|
# set a default root dir for each of our optional externals if it was not passed
|
||||||
set(OPTIONAL_EXTERNALS "LeapMotion")
|
set(OPTIONAL_EXTERNALS "LeapMotion")
|
||||||
|
@ -74,9 +72,7 @@ qt5_wrap_ui(QT_UI_HEADERS "${QT_UI_FILES}")
|
||||||
# add them to the interface source files
|
# add them to the interface source files
|
||||||
set(INTERFACE_SRCS ${INTERFACE_SRCS} "${QT_UI_HEADERS}" "${QT_RESOURCES}")
|
set(INTERFACE_SRCS ${INTERFACE_SRCS} "${QT_UI_HEADERS}" "${QT_RESOURCES}")
|
||||||
|
|
||||||
if (NOT DEV_BUILD)
|
|
||||||
list(APPEND INTERFACE_SRCS ${INTERFACE_QML_QRC})
|
list(APPEND INTERFACE_SRCS ${INTERFACE_QML_QRC})
|
||||||
endif()
|
|
||||||
|
|
||||||
if (UNIX)
|
if (UNIX)
|
||||||
install(
|
install(
|
||||||
|
|
|
@ -36,6 +36,7 @@ Rectangle {
|
||||||
property bool pendingInventoryReply: true;
|
property bool pendingInventoryReply: true;
|
||||||
property bool isShowingMyItems: false;
|
property bool isShowingMyItems: false;
|
||||||
property bool isDebuggingFirstUseTutorial: false;
|
property bool isDebuggingFirstUseTutorial: false;
|
||||||
|
property int pendingItemCount: 0;
|
||||||
// Style
|
// Style
|
||||||
color: hifi.colors.white;
|
color: hifi.colors.white;
|
||||||
Connections {
|
Connections {
|
||||||
|
@ -79,18 +80,22 @@ Rectangle {
|
||||||
onInventoryResult: {
|
onInventoryResult: {
|
||||||
purchasesReceived = true;
|
purchasesReceived = true;
|
||||||
|
|
||||||
if (root.pendingInventoryReply) {
|
|
||||||
inventoryTimer.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.status !== 'success') {
|
if (result.status !== 'success') {
|
||||||
console.log("Failed to get purchases", result.message);
|
console.log("Failed to get purchases", result.message);
|
||||||
} else {
|
} else if (!purchasesContentsList.dragging) { // Don't modify the view if the user's scrolling
|
||||||
var inventoryResult = processInventoryResult(result.data.assets);
|
var inventoryResult = processInventoryResult(result.data.assets);
|
||||||
|
|
||||||
|
var currentIndex = purchasesContentsList.currentIndex === -1 ? 0 : purchasesContentsList.currentIndex;
|
||||||
purchasesModel.clear();
|
purchasesModel.clear();
|
||||||
purchasesModel.append(inventoryResult);
|
purchasesModel.append(inventoryResult);
|
||||||
|
|
||||||
|
root.pendingItemCount = 0;
|
||||||
|
for (var i = 0; i < purchasesModel.count; i++) {
|
||||||
|
if (purchasesModel.get(i).status === "pending") {
|
||||||
|
root.pendingItemCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (previousPurchasesModel.count !== 0) {
|
if (previousPurchasesModel.count !== 0) {
|
||||||
checkIfAnyItemStatusChanged();
|
checkIfAnyItemStatusChanged();
|
||||||
} else {
|
} else {
|
||||||
|
@ -103,6 +108,12 @@ Rectangle {
|
||||||
previousPurchasesModel.append(inventoryResult);
|
previousPurchasesModel.append(inventoryResult);
|
||||||
|
|
||||||
buildFilteredPurchasesModel();
|
buildFilteredPurchasesModel();
|
||||||
|
|
||||||
|
purchasesContentsList.positionViewAtIndex(currentIndex, ListView.Beginning);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (root.pendingInventoryReply && root.pendingItemCount > 0) {
|
||||||
|
inventoryTimer.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
root.pendingInventoryReply = false;
|
root.pendingInventoryReply = false;
|
||||||
|
@ -419,6 +430,8 @@ Rectangle {
|
||||||
visible: (root.isShowingMyItems && filteredPurchasesModel.count !== 0) || (!root.isShowingMyItems && filteredPurchasesModel.count !== 0);
|
visible: (root.isShowingMyItems && filteredPurchasesModel.count !== 0) || (!root.isShowingMyItems && filteredPurchasesModel.count !== 0);
|
||||||
clip: true;
|
clip: true;
|
||||||
model: filteredPurchasesModel;
|
model: filteredPurchasesModel;
|
||||||
|
snapMode: ListView.SnapToItem;
|
||||||
|
highlightRangeMode: ListView.StrictlyEnforceRange;
|
||||||
// Anchors
|
// Anchors
|
||||||
anchors.top: root.canRezCertifiedItems ? separator.bottom : cantRezCertified.bottom;
|
anchors.top: root.canRezCertifiedItems ? separator.bottom : cantRezCertified.bottom;
|
||||||
anchors.topMargin: 12;
|
anchors.topMargin: 12;
|
||||||
|
|
|
@ -25,8 +25,12 @@ Item {
|
||||||
HifiConstants { id: hifi; }
|
HifiConstants { id: hifi; }
|
||||||
|
|
||||||
id: root;
|
id: root;
|
||||||
property bool historyReceived: false;
|
property bool initialHistoryReceived: false;
|
||||||
|
property bool historyRequestPending: true;
|
||||||
|
property bool noMoreHistoryData: false;
|
||||||
property int pendingCount: 0;
|
property int pendingCount: 0;
|
||||||
|
property int currentHistoryPage: 1;
|
||||||
|
property var pagesAlreadyAdded: new Array();
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: Commerce;
|
target: Commerce;
|
||||||
|
@ -36,8 +40,16 @@ Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
onHistoryResult : {
|
onHistoryResult : {
|
||||||
historyReceived = true;
|
root.initialHistoryReceived = true;
|
||||||
|
root.historyRequestPending = false;
|
||||||
|
|
||||||
if (result.status === 'success') {
|
if (result.status === 'success') {
|
||||||
|
var currentPage = parseInt(result.current_page);
|
||||||
|
|
||||||
|
if (result.data.history.length === 0) {
|
||||||
|
root.noMoreHistoryData = true;
|
||||||
|
console.log("No more data to retrieve from Commerce.history() endpoint.")
|
||||||
|
} else if (root.currentHistoryPage === 1) {
|
||||||
var sameItemCount = 0;
|
var sameItemCount = 0;
|
||||||
tempTransactionHistoryModel.clear();
|
tempTransactionHistoryModel.clear();
|
||||||
|
|
||||||
|
@ -60,10 +72,56 @@ Item {
|
||||||
}
|
}
|
||||||
calculatePendingAndInvalidated();
|
calculatePendingAndInvalidated();
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (root.pagesAlreadyAdded.indexOf(currentPage) !== -1) {
|
||||||
|
console.log("Page " + currentPage + " of history has already been added to the list.");
|
||||||
|
} else {
|
||||||
|
// First, add the history result to a temporary model
|
||||||
|
tempTransactionHistoryModel.clear();
|
||||||
|
tempTransactionHistoryModel.append(result.data.history);
|
||||||
|
|
||||||
|
// Make a note that we've already added this page to the model...
|
||||||
|
root.pagesAlreadyAdded.push(currentPage);
|
||||||
|
|
||||||
|
var insertionIndex = 0;
|
||||||
|
// If there's nothing in the model right now, we don't need to modify insertionIndex.
|
||||||
|
if (transactionHistoryModel.count !== 0) {
|
||||||
|
var currentIteratorPage;
|
||||||
|
// Search through the whole transactionHistoryModel and look for the insertion point.
|
||||||
|
// The insertion point is found when the result page from the server is less than
|
||||||
|
// the page that the current item came from, OR when we've reached the end of the whole model.
|
||||||
|
for (var i = 0; i < transactionHistoryModel.count; i++) {
|
||||||
|
currentIteratorPage = transactionHistoryModel.get(i).resultIsFromPage;
|
||||||
|
|
||||||
|
if (currentPage < currentIteratorPage) {
|
||||||
|
insertionIndex = i;
|
||||||
|
break;
|
||||||
|
} else if (i === transactionHistoryModel.count - 1) {
|
||||||
|
insertionIndex = i + 1;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go through the results we just got back from the server, setting the "resultIsFromPage"
|
||||||
|
// property of those results and adding them to the main model.
|
||||||
|
for (var i = 0; i < tempTransactionHistoryModel.count; i++) {
|
||||||
|
tempTransactionHistoryModel.setProperty(i, "resultIsFromPage", currentPage);
|
||||||
|
transactionHistoryModel.insert(i + insertionIndex, tempTransactionHistoryModel.get(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
calculatePendingAndInvalidated();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only auto-refresh if the user hasn't scrolled
|
||||||
|
// and there is more data to grab
|
||||||
|
if (transactionHistory.atYBeginning && !root.noMoreHistoryData) {
|
||||||
refreshTimer.start();
|
refreshTimer.start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: GlobalServices
|
target: GlobalServices
|
||||||
|
@ -134,9 +192,13 @@ Item {
|
||||||
|
|
||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
historyReceived = false;
|
transactionHistoryModel.clear();
|
||||||
Commerce.balance();
|
Commerce.balance();
|
||||||
Commerce.history();
|
initialHistoryReceived = false;
|
||||||
|
root.currentHistoryPage = 1;
|
||||||
|
root.noMoreHistoryData = false;
|
||||||
|
root.historyRequestPending = true;
|
||||||
|
Commerce.history(root.currentHistoryPage);
|
||||||
} else {
|
} else {
|
||||||
refreshTimer.stop();
|
refreshTimer.stop();
|
||||||
}
|
}
|
||||||
|
@ -164,9 +226,12 @@ Item {
|
||||||
id: refreshTimer;
|
id: refreshTimer;
|
||||||
interval: 4000;
|
interval: 4000;
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
console.log("Refreshing Wallet Home...");
|
if (transactionHistory.atYBeginning) {
|
||||||
|
console.log("Refreshing 1st Page of Recent Activity...");
|
||||||
|
root.historyRequestPending = true;
|
||||||
Commerce.balance();
|
Commerce.balance();
|
||||||
Commerce.history();
|
Commerce.history(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -241,7 +306,7 @@ Item {
|
||||||
anchors.right: parent.right;
|
anchors.right: parent.right;
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
visible: transactionHistoryModel.count === 0 && root.historyReceived;
|
visible: transactionHistoryModel.count === 0 && root.initialHistoryReceived;
|
||||||
anchors.centerIn: parent;
|
anchors.centerIn: parent;
|
||||||
width: parent.width - 12;
|
width: parent.width - 12;
|
||||||
height: parent.height;
|
height: parent.height;
|
||||||
|
@ -364,7 +429,12 @@ Item {
|
||||||
onAtYEndChanged: {
|
onAtYEndChanged: {
|
||||||
if (transactionHistory.atYEnd) {
|
if (transactionHistory.atYEnd) {
|
||||||
console.log("User scrolled to the bottom of 'Recent Activity'.");
|
console.log("User scrolled to the bottom of 'Recent Activity'.");
|
||||||
|
if (!root.historyRequestPending && !root.noMoreHistoryData) {
|
||||||
// Grab next page of results and append to model
|
// Grab next page of results and append to model
|
||||||
|
root.historyRequestPending = true;
|
||||||
|
Commerce.history(++root.currentHistoryPage);
|
||||||
|
console.log("Fetching Page " + root.currentHistoryPage + " of Recent Activity...");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -127,9 +127,15 @@ Item {
|
||||||
|
|
||||||
GridView {
|
GridView {
|
||||||
id: gridView
|
id: gridView
|
||||||
|
|
||||||
keyNavigationEnabled: false
|
keyNavigationEnabled: false
|
||||||
highlightFollowsCurrentItem: false
|
highlightFollowsCurrentItem: false
|
||||||
|
|
||||||
property int previousGridIndex: -1
|
property int previousGridIndex: -1
|
||||||
|
|
||||||
|
// true if any of the buttons contains mouse
|
||||||
|
property bool containsMouse: false
|
||||||
|
|
||||||
anchors {
|
anchors {
|
||||||
fill: parent
|
fill: parent
|
||||||
topMargin: 20
|
topMargin: 20
|
||||||
|
@ -162,15 +168,29 @@ Item {
|
||||||
flow: GridView.LeftToRight
|
flow: GridView.LeftToRight
|
||||||
model: page.proxyModel
|
model: page.proxyModel
|
||||||
|
|
||||||
delegate: Item {
|
delegate: Control {
|
||||||
id: wrapper
|
id: wrapper
|
||||||
width: gridView.cellWidth
|
width: gridView.cellWidth
|
||||||
height: gridView.cellHeight
|
height: gridView.cellHeight
|
||||||
|
|
||||||
|
hoverEnabled: true
|
||||||
|
|
||||||
|
property bool containsMouse: gridView.containsMouse
|
||||||
|
onHoveredChanged: {
|
||||||
|
if (hovered && !gridView.containsMouse) {
|
||||||
|
gridView.containsMouse = true
|
||||||
|
} else {
|
||||||
|
gridView.containsMouse = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
property var proxy: modelData
|
property var proxy: modelData
|
||||||
|
|
||||||
TabletButton {
|
TabletButton {
|
||||||
id: tabletButton
|
id: tabletButton
|
||||||
|
scale: wrapper.hovered ? 1.25 : wrapper.containsMouse ? 0.75 : 1.0
|
||||||
|
Behavior on scale { NumberAnimation { duration: 200; easing.type: Easing.Linear } }
|
||||||
|
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
gridView: wrapper.GridView.view
|
gridView: wrapper.GridView.view
|
||||||
buttonIndex: page.proxyModel.buttonIndex(uuid);
|
buttonIndex: page.proxyModel.buttonIndex(uuid);
|
||||||
|
@ -224,6 +244,7 @@ Item {
|
||||||
PageIndicator {
|
PageIndicator {
|
||||||
id: pageIndicator
|
id: pageIndicator
|
||||||
currentIndex: swipeView.currentIndex
|
currentIndex: swipeView.currentIndex
|
||||||
|
visible: swipeView.count > 1
|
||||||
|
|
||||||
delegate: Item {
|
delegate: Item {
|
||||||
width: 15
|
width: 15
|
||||||
|
|
|
@ -72,11 +72,16 @@ void Ledger::signedSend(const QString& propertyName, const QByteArray& text, con
|
||||||
send(endpoint, success, fail, QNetworkAccessManager::PutOperation, AccountManagerAuth::Required, request);
|
send(endpoint, success, fail, QNetworkAccessManager::PutOperation, AccountManagerAuth::Required, request);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Ledger::keysQuery(const QString& endpoint, const QString& success, const QString& fail) {
|
void Ledger::keysQuery(const QString& endpoint, const QString& success, const QString& fail, QJsonObject& requestParams) {
|
||||||
auto wallet = DependencyManager::get<Wallet>();
|
auto wallet = DependencyManager::get<Wallet>();
|
||||||
QJsonObject request;
|
requestParams["public_keys"] = QJsonArray::fromStringList(wallet->listPublicKeys());
|
||||||
request["public_keys"] = QJsonArray::fromStringList(wallet->listPublicKeys());
|
|
||||||
send(endpoint, success, fail, QNetworkAccessManager::PostOperation, AccountManagerAuth::Required, request);
|
send(endpoint, success, fail, QNetworkAccessManager::PostOperation, AccountManagerAuth::Required, requestParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Ledger::keysQuery(const QString& endpoint, const QString& success, const QString& fail) {
|
||||||
|
QJsonObject requestParams;
|
||||||
|
keysQuery(endpoint, success, fail, requestParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Ledger::buy(const QString& hfc_key, int cost, const QString& asset_id, const QString& inventory_key, const bool controlled_failure) {
|
void Ledger::buy(const QString& hfc_key, int cost, const QString& asset_id, const QString& inventory_key, const bool controlled_failure) {
|
||||||
|
@ -169,6 +174,7 @@ void Ledger::historySuccess(QNetworkReply& reply) {
|
||||||
QJsonObject newDataData;
|
QJsonObject newDataData;
|
||||||
newDataData["history"] = newHistoryArray;
|
newDataData["history"] = newHistoryArray;
|
||||||
newData["data"] = newDataData;
|
newData["data"] = newDataData;
|
||||||
|
newData["current_page"] = data["current_page"].toInt();
|
||||||
emit historyResult(newData);
|
emit historyResult(newData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,8 +182,11 @@ void Ledger::historyFailure(QNetworkReply& reply) {
|
||||||
failResponse("history", reply);
|
failResponse("history", reply);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Ledger::history(const QStringList& keys) {
|
void Ledger::history(const QStringList& keys, const int& pageNumber) {
|
||||||
keysQuery("history", "historySuccess", "historyFailure");
|
QJsonObject params;
|
||||||
|
params["per_page"] = 100;
|
||||||
|
params["page"] = pageNumber;
|
||||||
|
keysQuery("history", "historySuccess", "historyFailure", params);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The api/failResponse is called just for the side effect of logging.
|
// The api/failResponse is called just for the side effect of logging.
|
||||||
|
|
|
@ -29,7 +29,7 @@ public:
|
||||||
bool receiveAt(const QString& hfc_key, const QString& old_key);
|
bool receiveAt(const QString& hfc_key, const QString& old_key);
|
||||||
void balance(const QStringList& keys);
|
void balance(const QStringList& keys);
|
||||||
void inventory(const QStringList& keys);
|
void inventory(const QStringList& keys);
|
||||||
void history(const QStringList& keys);
|
void history(const QStringList& keys, const int& pageNumber);
|
||||||
void account();
|
void account();
|
||||||
void reset();
|
void reset();
|
||||||
void updateLocation(const QString& asset_id, const QString location, const bool controlledFailure = false);
|
void updateLocation(const QString& asset_id, const QString location, const bool controlledFailure = false);
|
||||||
|
@ -79,6 +79,7 @@ private:
|
||||||
QJsonObject apiResponse(const QString& label, QNetworkReply& reply);
|
QJsonObject apiResponse(const QString& label, QNetworkReply& reply);
|
||||||
QJsonObject failResponse(const QString& label, QNetworkReply& reply);
|
QJsonObject failResponse(const QString& label, QNetworkReply& reply);
|
||||||
void send(const QString& endpoint, const QString& success, const QString& fail, QNetworkAccessManager::Operation method, AccountManagerAuth::Type authType, QJsonObject request);
|
void send(const QString& endpoint, const QString& success, const QString& fail, QNetworkAccessManager::Operation method, AccountManagerAuth::Type authType, QJsonObject request);
|
||||||
|
void keysQuery(const QString& endpoint, const QString& success, const QString& fail, QJsonObject& extraRequestParams);
|
||||||
void keysQuery(const QString& endpoint, const QString& success, const QString& fail);
|
void keysQuery(const QString& endpoint, const QString& success, const QString& fail);
|
||||||
void signedSend(const QString& propertyName, const QByteArray& text, const QString& key, const QString& endpoint, const QString& success, const QString& fail, const bool controlled_failure = false);
|
void signedSend(const QString& propertyName, const QByteArray& text, const QString& key, const QString& endpoint, const QString& success, const QString& fail, const bool controlled_failure = false);
|
||||||
};
|
};
|
||||||
|
|
|
@ -96,12 +96,12 @@ void QmlCommerce::inventory() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void QmlCommerce::history() {
|
void QmlCommerce::history(const int& pageNumber) {
|
||||||
auto ledger = DependencyManager::get<Ledger>();
|
auto ledger = DependencyManager::get<Ledger>();
|
||||||
auto wallet = DependencyManager::get<Wallet>();
|
auto wallet = DependencyManager::get<Wallet>();
|
||||||
QStringList cachedPublicKeys = wallet->listPublicKeys();
|
QStringList cachedPublicKeys = wallet->listPublicKeys();
|
||||||
if (!cachedPublicKeys.isEmpty()) {
|
if (!cachedPublicKeys.isEmpty()) {
|
||||||
ledger->history(cachedPublicKeys);
|
ledger->history(cachedPublicKeys, pageNumber);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -60,7 +60,7 @@ protected:
|
||||||
Q_INVOKABLE void buy(const QString& assetId, int cost, const bool controlledFailure = false);
|
Q_INVOKABLE void buy(const QString& assetId, int cost, const bool controlledFailure = false);
|
||||||
Q_INVOKABLE void balance();
|
Q_INVOKABLE void balance();
|
||||||
Q_INVOKABLE void inventory();
|
Q_INVOKABLE void inventory();
|
||||||
Q_INVOKABLE void history();
|
Q_INVOKABLE void history(const int& pageNumber);
|
||||||
Q_INVOKABLE void generateKeyPair();
|
Q_INVOKABLE void generateKeyPair();
|
||||||
Q_INVOKABLE void reset();
|
Q_INVOKABLE void reset();
|
||||||
Q_INVOKABLE void resetLocalWalletOnly();
|
Q_INVOKABLE void resetLocalWalletOnly();
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
// libraries/midi/src
|
// libraries/midi/src
|
||||||
//
|
//
|
||||||
// Created by Burt Sloane
|
// Created by Burt Sloane
|
||||||
|
// Modified by Bruce Brown
|
||||||
// Copyright 2015 High Fidelity, Inc.
|
// Copyright 2015 High Fidelity, Inc.
|
||||||
//
|
//
|
||||||
// Distributed under the Apache License, Version 2.0.
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
@ -14,30 +15,45 @@
|
||||||
|
|
||||||
#include <QtCore/QLoggingCategory>
|
#include <QtCore/QLoggingCategory>
|
||||||
|
|
||||||
|
|
||||||
#if defined Q_OS_WIN32
|
#if defined Q_OS_WIN32
|
||||||
#include "Windows.h"
|
#include "Windows.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
#if defined Q_OS_WIN32
|
#if defined Q_OS_WIN32
|
||||||
const int MIDI_BYTE_MASK = 0x0FF;
|
const int MIDI_BYTE_MASK = 0x0FF;
|
||||||
|
const int MIDI_NIBBLE_MASK = 0x00F;
|
||||||
|
const int MIDI_PITCH_BEND_MASK = 0x3F80;
|
||||||
|
const int MIDI_SHIFT_STATUS = 4;
|
||||||
const int MIDI_SHIFT_NOTE = 8;
|
const int MIDI_SHIFT_NOTE = 8;
|
||||||
const int MIDI_SHIFT_VELOCITY = 16;
|
const int MIDI_SHIFT_VELOCITY = 16;
|
||||||
|
const int MIDI_SHIFT_PITCH_BEND = 9;
|
||||||
|
// Status Decode
|
||||||
|
const int MIDI_NOTE_OFF = 0x8;
|
||||||
|
const int MIDI_NOTE_ON = 0x9;
|
||||||
|
const int MIDI_POLYPHONIC_KEY_PRESSURE = 0xa;
|
||||||
|
const int MIDI_PROGRAM_CHANGE = 0xc;
|
||||||
|
const int MIDI_CHANNEL_PRESSURE = 0xd;
|
||||||
|
const int MIDI_PITCH_BEND_CHANGE = 0xe;
|
||||||
|
const int MIDI_SYSTEM_MESSAGE = 0xf;
|
||||||
#endif
|
#endif
|
||||||
const int MIDI_STATUS_MASK = 0x0F0;
|
|
||||||
const int MIDI_NOTE_OFF = 0x080;
|
|
||||||
const int MIDI_NOTE_ON = 0x090;
|
|
||||||
const int MIDI_CONTROL_CHANGE = 0x0b0;
|
|
||||||
const int MIDI_CHANNEL_MODE_ALL_NOTES_OFF = 0x07b;
|
|
||||||
|
|
||||||
|
const int MIDI_CONTROL_CHANGE = 0xb;
|
||||||
|
const int MIDI_CHANNEL_MODE_ALL_NOTES_OFF = 0x07b;
|
||||||
|
|
||||||
static Midi* instance = NULL; // communicate this to non-class callbacks
|
static Midi* instance = NULL; // communicate this to non-class callbacks
|
||||||
static bool thruModeEnabled = false;
|
static bool thruModeEnabled = false;
|
||||||
|
static bool broadcastEnabled = false;
|
||||||
|
static bool typeNoteOffEnabled = true;
|
||||||
|
static bool typeNoteOnEnabled = true;
|
||||||
|
static bool typePolyKeyPressureEnabled = false;
|
||||||
|
static bool typeControlChangeEnabled = true;
|
||||||
|
static bool typeProgramChangeEnabled = true;
|
||||||
|
static bool typeChanPressureEnabled = false;
|
||||||
|
static bool typePitchBendEnabled = true;
|
||||||
|
static bool typeSystemMessageEnabled = false;
|
||||||
|
|
||||||
std::vector<QString> Midi::midiinexclude;
|
std::vector<QString> Midi::midiInExclude;
|
||||||
std::vector<QString> Midi::midioutexclude;
|
std::vector<QString> Midi::midiOutExclude;
|
||||||
|
|
||||||
|
|
||||||
#if defined Q_OS_WIN32
|
#if defined Q_OS_WIN32
|
||||||
|
|
||||||
|
@ -47,7 +63,6 @@ std::vector<QString> Midi::midioutexclude;
|
||||||
std::vector<HMIDIIN> midihin;
|
std::vector<HMIDIIN> midihin;
|
||||||
std::vector<HMIDIOUT> midihout;
|
std::vector<HMIDIOUT> midihout;
|
||||||
|
|
||||||
|
|
||||||
void CALLBACK MidiInProc(HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) {
|
void CALLBACK MidiInProc(HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) {
|
||||||
switch (wMsg) {
|
switch (wMsg) {
|
||||||
case MIM_OPEN:
|
case MIM_OPEN:
|
||||||
|
@ -58,23 +73,64 @@ void CALLBACK MidiInProc(HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwInstance, DWORD
|
||||||
if (midihin[i] == hMidiIn) {
|
if (midihin[i] == hMidiIn) {
|
||||||
midihin[i] = NULL;
|
midihin[i] = NULL;
|
||||||
instance->allNotesOff();
|
instance->allNotesOff();
|
||||||
|
instance->midiHardwareChange();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case MIM_DATA: {
|
case MIM_DATA: {
|
||||||
int status = MIDI_BYTE_MASK & dwParam1;
|
int device = -1;
|
||||||
int note = MIDI_BYTE_MASK & (dwParam1 >> MIDI_SHIFT_NOTE);
|
for (int i = 0; i < midihin.size(); i++) {
|
||||||
int vel = MIDI_BYTE_MASK & (dwParam1 >> MIDI_SHIFT_VELOCITY);
|
if (midihin[i] == hMidiIn) {
|
||||||
if (thruModeEnabled) {
|
device = i;
|
||||||
instance->sendNote(status, note, vel); // relay the note on to all other midi devices
|
|
||||||
}
|
}
|
||||||
instance->noteReceived(status, note, vel); // notify the javascript
|
}
|
||||||
|
int raw = dwParam1;
|
||||||
|
int channel = (MIDI_NIBBLE_MASK & dwParam1) + 1;
|
||||||
|
int status = MIDI_BYTE_MASK & dwParam1;
|
||||||
|
int type = MIDI_NIBBLE_MASK & (dwParam1 >> MIDI_SHIFT_STATUS);
|
||||||
|
int note = MIDI_BYTE_MASK & (dwParam1 >> MIDI_SHIFT_NOTE);
|
||||||
|
int velocity = MIDI_BYTE_MASK & (dwParam1 >> MIDI_SHIFT_VELOCITY);
|
||||||
|
int bend = 0;
|
||||||
|
int program = 0;
|
||||||
|
if (!typeNoteOffEnabled && type == MIDI_NOTE_OFF) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!typeNoteOnEnabled && type == MIDI_NOTE_ON) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!typePolyKeyPressureEnabled && type == MIDI_POLYPHONIC_KEY_PRESSURE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!typeControlChangeEnabled && type == MIDI_CONTROL_CHANGE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeProgramChangeEnabled && type == MIDI_PROGRAM_CHANGE) {
|
||||||
|
program = note;
|
||||||
|
note = 0;
|
||||||
|
}
|
||||||
|
if (typeChanPressureEnabled && type == MIDI_CHANNEL_PRESSURE) {
|
||||||
|
velocity = note;
|
||||||
|
note = 0;
|
||||||
|
}
|
||||||
|
if (typePitchBendEnabled && type == MIDI_PITCH_BEND_CHANGE) {
|
||||||
|
bend = ((MIDI_BYTE_MASK & (dwParam1 >> MIDI_SHIFT_NOTE)) |
|
||||||
|
(MIDI_PITCH_BEND_MASK & (dwParam1 >> MIDI_SHIFT_PITCH_BEND))) - 8192;
|
||||||
|
channel = 0; // Weird values on different instruments
|
||||||
|
note = 0;
|
||||||
|
velocity = 0;
|
||||||
|
}
|
||||||
|
if (!typeSystemMessageEnabled && type == MIDI_SYSTEM_MESSAGE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (thruModeEnabled) {
|
||||||
|
instance->sendNote(status, note, velocity); // relay the message on to all other midi devices.
|
||||||
|
}
|
||||||
|
instance->midiReceived(device, raw, channel, status, type, note, velocity, bend, program); // notify the javascript
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void CALLBACK MidiOutProc(HMIDIOUT hmo, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) {
|
void CALLBACK MidiOutProc(HMIDIOUT hmo, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) {
|
||||||
switch (wMsg) {
|
switch (wMsg) {
|
||||||
case MOM_OPEN:
|
case MOM_OPEN:
|
||||||
|
@ -85,21 +141,45 @@ void CALLBACK MidiOutProc(HMIDIOUT hmo, UINT wMsg, DWORD_PTR dwInstance, DWORD_P
|
||||||
if (midihout[i] == hmo) {
|
if (midihout[i] == hmo) {
|
||||||
midihout[i] = NULL;
|
midihout[i] = NULL;
|
||||||
instance->allNotesOff();
|
instance->allNotesOff();
|
||||||
|
instance->midiHardwareChange();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Midi::sendRawMessage(int device, int raw) {
|
||||||
void Midi::sendNote(int status, int note, int vel) {
|
if (broadcastEnabled) {
|
||||||
for (int i = 0; i < midihout.size(); i++) {
|
for (int i = 0; i < midihout.size(); i++) {
|
||||||
if (midihout[i] != NULL) {
|
if (midihout[i] != NULL) {
|
||||||
midiOutShortMsg(midihout[i], status + (note << MIDI_SHIFT_NOTE) + (vel << MIDI_SHIFT_VELOCITY));
|
midiOutShortMsg(midihout[i], raw);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
midiOutShortMsg(midihout[device], raw);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Midi::sendMessage(int device, int channel, int type, int note, int velocity) {
|
||||||
|
int message = (channel - 1) | (type << MIDI_SHIFT_STATUS);
|
||||||
|
if (broadcastEnabled) {
|
||||||
|
for (int i = 0; i < midihout.size(); i++) {
|
||||||
|
if (midihout[i] != NULL) {
|
||||||
|
midiOutShortMsg(midihout[i], message | (note << MIDI_SHIFT_NOTE) | (velocity << MIDI_SHIFT_VELOCITY));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
midiOutShortMsg(midihout[device], message | (note << MIDI_SHIFT_NOTE) | (velocity << MIDI_SHIFT_VELOCITY));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Midi::sendNote(int status, int note, int velocity) {
|
||||||
|
for (int i = 0; i < midihout.size(); i++) {
|
||||||
|
if (midihout[i] != NULL) {
|
||||||
|
midiOutShortMsg(midihout[i], status + (note << MIDI_SHIFT_NOTE) + (velocity << MIDI_SHIFT_VELOCITY));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Midi::MidiSetup() {
|
void Midi::MidiSetup() {
|
||||||
midihin.clear();
|
midihin.clear();
|
||||||
|
@ -110,8 +190,8 @@ void Midi::MidiSetup() {
|
||||||
midiInGetDevCaps(i, &incaps, sizeof(MIDIINCAPS));
|
midiInGetDevCaps(i, &incaps, sizeof(MIDIINCAPS));
|
||||||
|
|
||||||
bool found = false;
|
bool found = false;
|
||||||
for (int j = 0; j < midiinexclude.size(); j++) {
|
for (int j = 0; j < midiInExclude.size(); j++) {
|
||||||
if (midiinexclude[j].toStdString().compare(incaps.szPname) == 0) {
|
if (midiInExclude[j].toStdString().compare(incaps.szPname) == 0) {
|
||||||
found = true;
|
found = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -122,7 +202,6 @@ void Midi::MidiSetup() {
|
||||||
midiInStart(tmphin);
|
midiInStart(tmphin);
|
||||||
midihin.push_back(tmphin);
|
midihin.push_back(tmphin);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MIDIOUTCAPS outcaps;
|
MIDIOUTCAPS outcaps;
|
||||||
|
@ -130,8 +209,8 @@ void Midi::MidiSetup() {
|
||||||
midiOutGetDevCaps(i, &outcaps, sizeof(MIDIINCAPS));
|
midiOutGetDevCaps(i, &outcaps, sizeof(MIDIINCAPS));
|
||||||
|
|
||||||
bool found = false;
|
bool found = false;
|
||||||
for (int j = 0; j < midioutexclude.size(); j++) {
|
for (int j = 0; j < midiOutExclude.size(); j++) {
|
||||||
if (midioutexclude[j].toStdString().compare(outcaps.szPname) == 0) {
|
if (midiOutExclude[j].toStdString().compare(outcaps.szPname) == 0) {
|
||||||
found = true;
|
found = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -164,7 +243,13 @@ void Midi::MidiCleanup() {
|
||||||
midihout.clear();
|
midihout.clear();
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
void Midi::sendNote(int status, int note, int vel) {
|
void Midi::sendRawMessage(int device, int raw) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void Midi::sendNote(int status, int note, int velocity) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void Midi::sendMessage(int device, int channel, int type, int note, int velocity){
|
||||||
}
|
}
|
||||||
|
|
||||||
void Midi::MidiSetup() {
|
void Midi::MidiSetup() {
|
||||||
|
@ -176,26 +261,30 @@ void Midi::MidiCleanup() {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void Midi::noteReceived(int status, int note, int velocity) {
|
void Midi::midiReceived(int device, int raw, int channel, int status, int type, int note, int velocity, int bend, int program) {
|
||||||
if (((status & MIDI_STATUS_MASK) != MIDI_NOTE_OFF) &&
|
|
||||||
((status & MIDI_STATUS_MASK) != MIDI_NOTE_ON) &&
|
|
||||||
((status & MIDI_STATUS_MASK) != MIDI_CONTROL_CHANGE)) {
|
|
||||||
return; // NOTE: only sending note-on, note-off, and control-change to Javascript
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariantMap eventData;
|
QVariantMap eventData;
|
||||||
|
eventData["device"] = device;
|
||||||
|
eventData["raw"] = raw;
|
||||||
|
eventData["channel"] = channel;
|
||||||
eventData["status"] = status;
|
eventData["status"] = status;
|
||||||
|
eventData["type"] = type;
|
||||||
eventData["note"] = note;
|
eventData["note"] = note;
|
||||||
eventData["velocity"] = velocity;
|
eventData["velocity"] = velocity;
|
||||||
emit midiNote(eventData);
|
eventData["bend"] = bend;
|
||||||
|
eventData["program"] = program;
|
||||||
|
emit midiNote(eventData);// Legacy
|
||||||
|
emit midiMessage(eventData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Midi::midiHardwareChange() {
|
||||||
|
emit midiReset();
|
||||||
|
}
|
||||||
//
|
//
|
||||||
|
|
||||||
Midi::Midi() {
|
Midi::Midi() {
|
||||||
instance = this;
|
instance = this;
|
||||||
#if defined Q_OS_WIN32
|
#if defined Q_OS_WIN32
|
||||||
midioutexclude.push_back("Microsoft GS Wavetable Synth"); // we don't want to hear this thing
|
midiOutExclude.push_back("Microsoft GS Wavetable Synth"); // we don't want to hear this thing (Lags)
|
||||||
#endif
|
#endif
|
||||||
MidiSetup();
|
MidiSetup();
|
||||||
}
|
}
|
||||||
|
@ -203,10 +292,18 @@ Midi::Midi() {
|
||||||
Midi::~Midi() {
|
Midi::~Midi() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Midi::sendRawDword(int device, int raw) {
|
||||||
|
sendRawMessage(device, raw);
|
||||||
|
}
|
||||||
|
|
||||||
void Midi::playMidiNote(int status, int note, int velocity) {
|
void Midi::playMidiNote(int status, int note, int velocity) {
|
||||||
sendNote(status, note, velocity);
|
sendNote(status, note, velocity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Midi::sendMidiMessage(int device, int channel, int type, int note, int velocity) {
|
||||||
|
sendMessage(device, channel, type, note, velocity);
|
||||||
|
}
|
||||||
|
|
||||||
void Midi::allNotesOff() {
|
void Midi::allNotesOff() {
|
||||||
sendNote(MIDI_CONTROL_CHANGE, MIDI_CHANNEL_MODE_ALL_NOTES_OFF, 0); // all notes off
|
sendNote(MIDI_CONTROL_CHANGE, MIDI_CHANNEL_MODE_ALL_NOTES_OFF, 0); // all notes off
|
||||||
}
|
}
|
||||||
|
@ -219,6 +316,7 @@ void Midi::resetDevices() {
|
||||||
void Midi::USBchanged() {
|
void Midi::USBchanged() {
|
||||||
instance->MidiCleanup();
|
instance->MidiCleanup();
|
||||||
instance->MidiSetup();
|
instance->MidiSetup();
|
||||||
|
instance->midiHardwareChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -245,16 +343,16 @@ QStringList Midi::listMidiDevices(bool output) {
|
||||||
|
|
||||||
void Midi::unblockMidiDevice(QString name, bool output) {
|
void Midi::unblockMidiDevice(QString name, bool output) {
|
||||||
if (output) {
|
if (output) {
|
||||||
for (unsigned long i = 0; i < midioutexclude.size(); i++) {
|
for (unsigned long i = 0; i < midiOutExclude.size(); i++) {
|
||||||
if (midioutexclude[i].toStdString().compare(name.toStdString()) == 0) {
|
if (midiOutExclude[i].toStdString().compare(name.toStdString()) == 0) {
|
||||||
midioutexclude.erase(midioutexclude.begin() + i);
|
midiOutExclude.erase(midiOutExclude.begin() + i);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (unsigned long i = 0; i < midiinexclude.size(); i++) {
|
for (unsigned long i = 0; i < midiInExclude.size(); i++) {
|
||||||
if (midiinexclude[i].toStdString().compare(name.toStdString()) == 0) {
|
if (midiInExclude[i].toStdString().compare(name.toStdString()) == 0) {
|
||||||
midiinexclude.erase(midiinexclude.begin() + i);
|
midiInExclude.erase(midiInExclude.begin() + i);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -264,9 +362,9 @@ void Midi::unblockMidiDevice(QString name, bool output) {
|
||||||
void Midi::blockMidiDevice(QString name, bool output) {
|
void Midi::blockMidiDevice(QString name, bool output) {
|
||||||
unblockMidiDevice(name, output); // make sure it's only in there once
|
unblockMidiDevice(name, output); // make sure it's only in there once
|
||||||
if (output) {
|
if (output) {
|
||||||
midioutexclude.push_back(name);
|
midiOutExclude.push_back(name);
|
||||||
} else {
|
} else {
|
||||||
midiinexclude.push_back(name);
|
midiInExclude.push_back(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -274,3 +372,38 @@ void Midi::thruModeEnable(bool enable) {
|
||||||
thruModeEnabled = enable;
|
thruModeEnabled = enable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Midi::broadcastEnable(bool enable) {
|
||||||
|
broadcastEnabled = enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Midi::typeNoteOffEnable(bool enable) {
|
||||||
|
typeNoteOffEnabled = enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Midi::typeNoteOnEnable(bool enable) {
|
||||||
|
typeNoteOnEnabled = enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Midi::typePolyKeyPressureEnable(bool enable) {
|
||||||
|
typePolyKeyPressureEnabled = enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Midi::typeControlChangeEnable(bool enable) {
|
||||||
|
typeControlChangeEnabled = enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Midi::typeProgramChangeEnable(bool enable) {
|
||||||
|
typeProgramChangeEnabled = enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Midi::typeChanPressureEnable(bool enable) {
|
||||||
|
typeChanPressureEnabled = enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Midi::typePitchBendEnable(bool enable) {
|
||||||
|
typePitchBendEnabled = enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Midi::typeSystemMessageEnable(bool enable) {
|
||||||
|
typeSystemMessageEnabled = enable;
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
// libraries/midi/src
|
// libraries/midi/src
|
||||||
//
|
//
|
||||||
// Created by Burt Sloane
|
// Created by Burt Sloane
|
||||||
|
// Modified by Bruce Brown
|
||||||
// Copyright 2015 High Fidelity, Inc.
|
// Copyright 2015 High Fidelity, Inc.
|
||||||
//
|
//
|
||||||
// Distributed under the Apache License, Version 2.0.
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
@ -24,13 +25,16 @@ class Midi : public QObject, public Dependency {
|
||||||
SINGLETON_DEPENDENCY
|
SINGLETON_DEPENDENCY
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void noteReceived(int status, int note, int velocity); // relay a note to Javascript
|
void midiReceived(int device, int raw, int channel, int status, int type, int note, int velocity, int bend, int program); // relay a note to Javascript
|
||||||
void sendNote(int status, int note, int vel); // relay a note to MIDI outputs
|
void midiHardwareChange(); // relay hardware change to Javascript
|
||||||
|
void sendRawMessage(int device, int raw); // relay midi message to MIDI outputs
|
||||||
|
void sendNote(int status, int note, int velocity); // relay a note to MIDI outputs
|
||||||
|
void sendMessage(int device, int channel, int type, int note, int velocity); // relay a message to MIDI outputs
|
||||||
static void USBchanged();
|
static void USBchanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static std::vector<QString> midiinexclude;
|
static std::vector<QString> midiInExclude;
|
||||||
static std::vector<QString> midioutexclude;
|
static std::vector<QString> midiOutExclude;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void MidiSetup();
|
void MidiSetup();
|
||||||
|
@ -38,13 +42,31 @@ private:
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void midiNote(QVariantMap eventData);
|
void midiNote(QVariantMap eventData);
|
||||||
|
void midiMessage(QVariantMap eventData);
|
||||||
|
void midiReset();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
// Send Raw Midi Packet to all connected devices
|
||||||
|
Q_INVOKABLE void sendRawDword(int device, int raw);
|
||||||
|
/// Send Raw Midi message to selected device
|
||||||
|
/// @param {int} device: device number
|
||||||
|
/// @param {int} raw: raw midi message (DWORD)
|
||||||
|
|
||||||
|
// Send Midi Message to all connected devices
|
||||||
|
Q_INVOKABLE void sendMidiMessage(int device, int channel, int type, int note, int velocity);
|
||||||
|
/// Send midi message to selected device/devices
|
||||||
|
/// @param {int} device: device number
|
||||||
|
/// @param {int} channel: channel number
|
||||||
|
/// @param {int} type: 0x8 is noteoff, 0x9 is noteon (if velocity=0, noteoff), etc
|
||||||
|
/// @param {int} note: midi note number
|
||||||
|
/// @param {int} velocity: note velocity (0 means noteoff)
|
||||||
|
|
||||||
|
// Send Midi Message to all connected devices
|
||||||
|
Q_INVOKABLE void playMidiNote(int status, int note, int velocity);
|
||||||
/// play a note on all connected devices
|
/// play a note on all connected devices
|
||||||
/// @param {int} status: 0x80 is noteoff, 0x90 is noteon (if velocity=0, noteoff), etc
|
/// @param {int} status: 0x80 is noteoff, 0x90 is noteon (if velocity=0, noteoff), etc
|
||||||
/// @param {int} note: midi note number
|
/// @param {int} note: midi note number
|
||||||
/// @param {int} velocity: note velocity (0 means noteoff)
|
/// @param {int} velocity: note velocity (0 means noteoff)
|
||||||
Q_INVOKABLE void playMidiNote(int status, int note, int velocity);
|
|
||||||
|
|
||||||
/// turn off all notes on all connected devices
|
/// turn off all notes on all connected devices
|
||||||
Q_INVOKABLE void allNotesOff();
|
Q_INVOKABLE void allNotesOff();
|
||||||
|
@ -64,6 +86,20 @@ Q_INVOKABLE void unblockMidiDevice(QString name, bool output);
|
||||||
/// repeat all incoming notes to all outputs (default disabled)
|
/// repeat all incoming notes to all outputs (default disabled)
|
||||||
Q_INVOKABLE void thruModeEnable(bool enable);
|
Q_INVOKABLE void thruModeEnable(bool enable);
|
||||||
|
|
||||||
|
/// broadcast on all unblocked devices
|
||||||
|
Q_INVOKABLE void broadcastEnable(bool enable);
|
||||||
|
|
||||||
|
/// filter by event types
|
||||||
|
Q_INVOKABLE void typeNoteOffEnable(bool enable);
|
||||||
|
Q_INVOKABLE void typeNoteOnEnable(bool enable);
|
||||||
|
Q_INVOKABLE void typePolyKeyPressureEnable(bool enable);
|
||||||
|
Q_INVOKABLE void typeControlChangeEnable(bool enable);
|
||||||
|
Q_INVOKABLE void typeProgramChangeEnable(bool enable);
|
||||||
|
Q_INVOKABLE void typeChanPressureEnable(bool enable);
|
||||||
|
Q_INVOKABLE void typePitchBendEnable(bool enable);
|
||||||
|
Q_INVOKABLE void typeSystemMessageEnable(bool enable);
|
||||||
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Midi();
|
Midi();
|
||||||
virtual ~Midi();
|
virtual ~Midi();
|
||||||
|
|
|
@ -262,7 +262,7 @@ controller::Pose ovrControllerPoseToHandPose(
|
||||||
pose.translation = toGlm(handPose.ThePose.Position);
|
pose.translation = toGlm(handPose.ThePose.Position);
|
||||||
pose.translation += rotation * translationOffset;
|
pose.translation += rotation * translationOffset;
|
||||||
pose.rotation = rotation * rotationOffset;
|
pose.rotation = rotation * rotationOffset;
|
||||||
pose.angularVelocity = toGlm(handPose.AngularVelocity);
|
pose.angularVelocity = rotation * toGlm(handPose.AngularVelocity);
|
||||||
pose.velocity = toGlm(handPose.LinearVelocity);
|
pose.velocity = toGlm(handPose.LinearVelocity);
|
||||||
pose.valid = true;
|
pose.valid = true;
|
||||||
return pose;
|
return pose;
|
||||||
|
|
|
@ -29,7 +29,8 @@ var DEFAULT_SCRIPTS_COMBINED = [
|
||||||
"system/notifications.js",
|
"system/notifications.js",
|
||||||
"system/dialTone.js",
|
"system/dialTone.js",
|
||||||
"system/firstPersonHMD.js",
|
"system/firstPersonHMD.js",
|
||||||
"system/tablet-ui/tabletUI.js"
|
"system/tablet-ui/tabletUI.js",
|
||||||
|
"system/emote.js"
|
||||||
];
|
];
|
||||||
var DEFAULT_SCRIPTS_SEPARATE = [
|
var DEFAULT_SCRIPTS_SEPARATE = [
|
||||||
"system/controllers/controllerScripts.js"
|
"system/controllers/controllerScripts.js"
|
||||||
|
|
BIN
scripts/system/assets/animations/Cheering.fbx
Normal file
BIN
scripts/system/assets/animations/Cheering.fbx
Normal file
Binary file not shown.
BIN
scripts/system/assets/animations/Clapping.fbx
Normal file
BIN
scripts/system/assets/animations/Clapping.fbx
Normal file
Binary file not shown.
BIN
scripts/system/assets/animations/Crying.fbx
Normal file
BIN
scripts/system/assets/animations/Crying.fbx
Normal file
Binary file not shown.
BIN
scripts/system/assets/animations/Dancing.fbx
Normal file
BIN
scripts/system/assets/animations/Dancing.fbx
Normal file
Binary file not shown.
BIN
scripts/system/assets/animations/Fall.fbx
Normal file
BIN
scripts/system/assets/animations/Fall.fbx
Normal file
Binary file not shown.
BIN
scripts/system/assets/animations/Pointing.fbx
Normal file
BIN
scripts/system/assets/animations/Pointing.fbx
Normal file
Binary file not shown.
BIN
scripts/system/assets/animations/Surprised.fbx
Normal file
BIN
scripts/system/assets/animations/Surprised.fbx
Normal file
Binary file not shown.
BIN
scripts/system/assets/animations/Waving.fbx
Normal file
BIN
scripts/system/assets/animations/Waving.fbx
Normal file
Binary file not shown.
BIN
scripts/system/assets/sounds/rezzing.wav
Normal file
BIN
scripts/system/assets/sounds/rezzing.wav
Normal file
Binary file not shown.
|
@ -225,7 +225,7 @@ function adjustPositionPerBoundingBox(position, direction, registration, dimensi
|
||||||
|
|
||||||
var TOOLS_PATH = Script.resolvePath("assets/images/tools/");
|
var TOOLS_PATH = Script.resolvePath("assets/images/tools/");
|
||||||
var GRABBABLE_ENTITIES_MENU_CATEGORY = "Edit";
|
var GRABBABLE_ENTITIES_MENU_CATEGORY = "Edit";
|
||||||
var GRABBABLE_ENTITIES_MENU_ITEM = "Create Entities As Grabbable";
|
var GRABBABLE_ENTITIES_MENU_ITEM = "Create Entities As Grabbable (except Zones, Particles, and Lights)";
|
||||||
|
|
||||||
var toolBar = (function () {
|
var toolBar = (function () {
|
||||||
var EDIT_SETTING = "io.highfidelity.isEditing"; // for communication with other scripts
|
var EDIT_SETTING = "io.highfidelity.isEditing"; // for communication with other scripts
|
||||||
|
@ -239,6 +239,7 @@ var toolBar = (function () {
|
||||||
var dimensions = properties.dimensions ? properties.dimensions : DEFAULT_DIMENSIONS;
|
var dimensions = properties.dimensions ? properties.dimensions : DEFAULT_DIMENSIONS;
|
||||||
var position = getPositionToCreateEntity();
|
var position = getPositionToCreateEntity();
|
||||||
var entityID = null;
|
var entityID = null;
|
||||||
|
|
||||||
if (position !== null && position !== undefined) {
|
if (position !== null && position !== undefined) {
|
||||||
var direction;
|
var direction;
|
||||||
if (Camera.mode === "entity" || Camera.mode === "independent") {
|
if (Camera.mode === "entity" || Camera.mode === "independent") {
|
||||||
|
@ -278,9 +279,13 @@ var toolBar = (function () {
|
||||||
|
|
||||||
position = grid.snapToSurface(grid.snapToGrid(position, false, dimensions), dimensions);
|
position = grid.snapToSurface(grid.snapToGrid(position, false, dimensions), dimensions);
|
||||||
properties.position = position;
|
properties.position = position;
|
||||||
if (Menu.isOptionChecked(GRABBABLE_ENTITIES_MENU_ITEM)) {
|
if (Menu.isOptionChecked(GRABBABLE_ENTITIES_MENU_ITEM) &&
|
||||||
|
!(properties.type === "Zone" || properties.type === "Light" || properties.type === "ParticleEffect")) {
|
||||||
properties.userData = JSON.stringify({ grabbableKey: { grabbable: true } });
|
properties.userData = JSON.stringify({ grabbableKey: { grabbable: true } });
|
||||||
|
} else {
|
||||||
|
properties.userData = JSON.stringify({ grabbableKey: { grabbable: false } });
|
||||||
}
|
}
|
||||||
|
|
||||||
entityID = Entities.addEntity(properties);
|
entityID = Entities.addEntity(properties);
|
||||||
|
|
||||||
if (properties.type === "ParticleEffect") {
|
if (properties.type === "ParticleEffect") {
|
||||||
|
|
122
scripts/system/emote.js
Normal file
122
scripts/system/emote.js
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
//
|
||||||
|
// emote.js
|
||||||
|
// scripts/system/
|
||||||
|
//
|
||||||
|
// Created by Brad Hefta-Gaub on 7 Jan 2018
|
||||||
|
// Copyright 2018 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
|
||||||
|
//
|
||||||
|
/* globals Script, Tablet */
|
||||||
|
/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */
|
||||||
|
|
||||||
|
(function() { // BEGIN LOCAL_SCOPE
|
||||||
|
|
||||||
|
|
||||||
|
var EMOTE_ANIMATIONS = ['Crying', 'Surprised', 'Dancing', 'Cheering', 'Waving', 'Fall', 'Pointing', 'Clapping'];
|
||||||
|
var ANIMATIONS = Array();
|
||||||
|
|
||||||
|
|
||||||
|
EMOTE_ANIMATIONS.forEach(function (name) {
|
||||||
|
var animationURL = Script.resolvePath("assets/animations/" + name + ".fbx");
|
||||||
|
var resource = AnimationCache.prefetch(animationURL);
|
||||||
|
var animation = AnimationCache.getAnimation(animationURL);
|
||||||
|
ANIMATIONS[name] = { url: animationURL, animation: animation, resource: resource};
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
var EMOTE_APP_BASE = "html/EmoteApp.html";
|
||||||
|
var EMOTE_APP_URL = Script.resolvePath(EMOTE_APP_BASE);
|
||||||
|
var EMOTE_LABEL = "EMOTE";
|
||||||
|
var EMOTE_APP_SORT_ORDER = 11;
|
||||||
|
var FPS = 60;
|
||||||
|
var MSEC_PER_SEC = 1000;
|
||||||
|
var FINISHED = 3; // see ScriptableResource::State
|
||||||
|
|
||||||
|
var onEmoteScreen = false;
|
||||||
|
var button;
|
||||||
|
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||||
|
var activeTimer = false; // used to cancel active timer if a user plays an amimation while another animation is playing
|
||||||
|
var activeEmote = false; // to keep track of the currently playing emote
|
||||||
|
|
||||||
|
button = tablet.addButton({
|
||||||
|
//icon: "icons/tablet-icons/emote.svg", // TODO - we need graphics for this
|
||||||
|
text: EMOTE_LABEL,
|
||||||
|
sortOrder: EMOTE_APP_SORT_ORDER
|
||||||
|
});
|
||||||
|
|
||||||
|
function onClicked() {
|
||||||
|
if (onEmoteScreen) {
|
||||||
|
tablet.gotoHomeScreen();
|
||||||
|
} else {
|
||||||
|
onEmoteScreen = true;
|
||||||
|
tablet.gotoWebScreen(EMOTE_APP_URL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onScreenChanged(type, url) {
|
||||||
|
onEmoteScreen = type === "Web" && (url.indexOf(EMOTE_APP_BASE) == url.length - EMOTE_APP_BASE.length);
|
||||||
|
button.editProperties({ isActive: onEmoteScreen });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle the events we're receiving from the web UI
|
||||||
|
function onWebEventReceived(event) {
|
||||||
|
|
||||||
|
// Converts the event to a JavasScript Object
|
||||||
|
if (typeof event === "string") {
|
||||||
|
event = JSON.parse(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.type === "click") {
|
||||||
|
var emoteName = event.data;
|
||||||
|
|
||||||
|
if (ANIMATIONS[emoteName].resource.state == FINISHED) {
|
||||||
|
if (activeTimer !== false) {
|
||||||
|
Script.clearTimeout(activeTimer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the activeEmote is different from the chosen emote, then play the new emote. Other wise,
|
||||||
|
// this is a second click on the same emote as the activeEmote, and we will just stop it.
|
||||||
|
if (activeEmote !== emoteName) {
|
||||||
|
activeEmote = emoteName;
|
||||||
|
var frameCount = ANIMATIONS[emoteName].animation.frames.length;
|
||||||
|
MyAvatar.overrideAnimation(ANIMATIONS[emoteName].url, FPS, false, 0, frameCount);
|
||||||
|
|
||||||
|
var timeOut = MSEC_PER_SEC * frameCount / FPS;
|
||||||
|
activeTimer = Script.setTimeout(function () {
|
||||||
|
MyAvatar.restoreAnimation();
|
||||||
|
activeTimer = false;
|
||||||
|
activeEmote = false;
|
||||||
|
}, timeOut);
|
||||||
|
} else {
|
||||||
|
activeEmote = false;
|
||||||
|
MyAvatar.restoreAnimation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button.clicked.connect(onClicked);
|
||||||
|
tablet.screenChanged.connect(onScreenChanged);
|
||||||
|
tablet.webEventReceived.connect(onWebEventReceived);
|
||||||
|
|
||||||
|
Script.scriptEnding.connect(function () {
|
||||||
|
if (onEmoteScreen) {
|
||||||
|
tablet.gotoHomeScreen();
|
||||||
|
}
|
||||||
|
button.clicked.disconnect(onClicked);
|
||||||
|
tablet.screenChanged.disconnect(onScreenChanged);
|
||||||
|
if (tablet) {
|
||||||
|
tablet.removeButton(button);
|
||||||
|
}
|
||||||
|
if (activeTimer !== false) {
|
||||||
|
Script.clearTimeout(activeTimer);
|
||||||
|
MyAvatar.restoreAnimation();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
}()); // END LOCAL_SCOPE
|
136
scripts/system/html/EmoteApp.html
Normal file
136
scripts/system/html/EmoteApp.html
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
<!--
|
||||||
|
// EmoteApp.html
|
||||||
|
//
|
||||||
|
// Created by Brad Hefta-Gaub on 7 Jan 2018
|
||||||
|
// Copyright 2018 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
|
||||||
|
-->
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Emote App</title>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=Raleway:300,400,600,700"" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
|
font-family: 'Raleway', sans-serif;
|
||||||
|
color: white;
|
||||||
|
background: linear-gradient(#2b2b2b, #0f212e);
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-bar {
|
||||||
|
height: 90px;
|
||||||
|
background: linear-gradient(#2b2b2b, #1e1e1e);
|
||||||
|
font-weight: bold;
|
||||||
|
padding-left: 30px;
|
||||||
|
padding-right: 30px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
position: fixed;
|
||||||
|
width: 480px;
|
||||||
|
top: 0;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
margin-top: 90px;
|
||||||
|
padding: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=button] {
|
||||||
|
font-family: 'Raleway';
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 13px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
vertical-align: top;
|
||||||
|
height: 28px;
|
||||||
|
min-width: 120px;
|
||||||
|
padding: 0px 18px;
|
||||||
|
margin-right: 6px;
|
||||||
|
border-radius: 5px;
|
||||||
|
border: none;
|
||||||
|
color: #fff;
|
||||||
|
background-color: #000;
|
||||||
|
background: linear-gradient(#343434 20%, #000 100%);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=button].white {
|
||||||
|
color: #121212;
|
||||||
|
background-color: #afafaf;
|
||||||
|
background: linear-gradient(#fff 20%, #afafaf 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=button]:enabled:hover {
|
||||||
|
background: linear-gradient(#000, #000);
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
input[type=button].white:enabled:hover {
|
||||||
|
background: linear-gradient(#fff, #fff);
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=button]:active {
|
||||||
|
background: linear-gradient(#343434, #343434);
|
||||||
|
}
|
||||||
|
input[type=button].white:active {
|
||||||
|
background: linear-gradient(#afafaf, #afafaf);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=button]:disabled {
|
||||||
|
color: #252525;
|
||||||
|
background: linear-gradient(#575757 20%, #252525 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=button][pressed=pressed] {
|
||||||
|
color: #00b4ef;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="top-bar">
|
||||||
|
<h4>Emote App</h4>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<p>Click an emotion to Emote:<p>
|
||||||
|
<p><input type="button" class="emote-button white" value="Crying"></p>
|
||||||
|
<p><input type="button" class="emote-button white" value="Surprised"></p>
|
||||||
|
<p><input type="button" class="emote-button white" value="Dancing"></p>
|
||||||
|
<p><input type="button" class="emote-button white" value="Cheering"></p>
|
||||||
|
<p><input type="button" class="emote-button white" value="Waving"></p>
|
||||||
|
<p><input type="button" class="emote-button white" value="Fall"></p>
|
||||||
|
<p><input type="button" class="emote-button white" value="Pointing"></p>
|
||||||
|
<p><input type="button" class="emote-button white" value="Clapping"></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
|
||||||
|
<script>
|
||||||
|
function main() {
|
||||||
|
// Send an event to emote.js when the page loads and is ready to get things rolling
|
||||||
|
console.log("document ready");
|
||||||
|
var readyEvent = {
|
||||||
|
"type": "ready",
|
||||||
|
};
|
||||||
|
// The event bridge handles event represented as a string the best. So here we first create a Javascript object, then convert to stirng
|
||||||
|
EventBridge.emitWebEvent(JSON.stringify(readyEvent));
|
||||||
|
|
||||||
|
// Send an event when user click on each of the emote buttons
|
||||||
|
$(".emote-button").click(function(){
|
||||||
|
console.log(this.value + " button click");
|
||||||
|
var clickEvent = {
|
||||||
|
"type": "click",
|
||||||
|
"data": this.value
|
||||||
|
};
|
||||||
|
EventBridge.emitWebEvent(JSON.stringify(clickEvent));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(main);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -28,6 +28,7 @@ var selectionDisplay = null; // for gridTool.js to ignore
|
||||||
var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/purchases/Purchases.qml";
|
var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/purchases/Purchases.qml";
|
||||||
var MARKETPLACE_WALLET_QML_PATH = "hifi/commerce/wallet/Wallet.qml";
|
var MARKETPLACE_WALLET_QML_PATH = "hifi/commerce/wallet/Wallet.qml";
|
||||||
var MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH = "commerce/inspectionCertificate/InspectionCertificate.qml";
|
var MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH = "commerce/inspectionCertificate/InspectionCertificate.qml";
|
||||||
|
var REZZING_SOUND = SoundCache.getSound(Script.resolvePath("../assets/sounds/rezzing.wav"));
|
||||||
|
|
||||||
var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png";
|
var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png";
|
||||||
// var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png";
|
// var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png";
|
||||||
|
@ -341,6 +342,15 @@ var selectionDisplay = null; // for gridTool.js to ignore
|
||||||
// we currently assume a wearable is a single entity
|
// we currently assume a wearable is a single entity
|
||||||
Entities.editEntity(pastedEntityIDs[0], offsets);
|
Entities.editEntity(pastedEntityIDs[0], offsets);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var rezPosition = Entities.getEntityProperties(pastedEntityIDs[0], "position").position;
|
||||||
|
|
||||||
|
Audio.playSound(REZZING_SOUND, {
|
||||||
|
volume: 1.0,
|
||||||
|
position: rezPosition,
|
||||||
|
localOnly: true
|
||||||
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
Window.notifyEditError("Can't import entities: entities would be out of bounds.");
|
Window.notifyEditError("Can't import entities: entities would be out of bounds.");
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue