diff --git a/interface/resources/fonts/vircadia_glyphs.ttf b/interface/resources/fonts/vircadia_glyphs.ttf new file mode 100644 index 0000000000..b6a8c2e7ad Binary files /dev/null and b/interface/resources/fonts/vircadia_glyphs.ttf differ diff --git a/scripts/system/create/audioFeedback/audioFeedback.js b/scripts/system/create/audioFeedback/audioFeedback.js new file mode 100644 index 0000000000..f1900d5716 --- /dev/null +++ b/scripts/system/create/audioFeedback/audioFeedback.js @@ -0,0 +1,34 @@ +// +// audioFeedback.js +// +// Created by Alezia Kurdis on September 30, 2020. +// Copyright 2020 Vircadia contributors. +// +// This script add audio feedback (confirmation and rejection) for user interactions that require one. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +audioFeedback = (function() { + var that = {}; + + var confirmationSound = SoundCache.getSound(Script.resolvePath("./sounds/confirmation.mp3")); + var rejectionSound = SoundCache.getSound(Script.resolvePath("./sounds/rejection.mp3")); + + that.confirmation = function() { //Play a confirmation sound + var injector = Audio.playSound(confirmationSound, { + "volume": 0.3, + "localOnly": true + }); + } + + that.rejection = function() { //Play a rejection sound + var injector = Audio.playSound(rejectionSound, { + "volume": 0.3, + "localOnly": true + }); + } + + return that; +})(); diff --git a/scripts/system/create/audioFeedback/sounds/confirmation.mp3 b/scripts/system/create/audioFeedback/sounds/confirmation.mp3 new file mode 100644 index 0000000000..39143fce4f Binary files /dev/null and b/scripts/system/create/audioFeedback/sounds/confirmation.mp3 differ diff --git a/scripts/system/create/audioFeedback/sounds/rejection.mp3 b/scripts/system/create/audioFeedback/sounds/rejection.mp3 new file mode 100644 index 0000000000..46e564dcdc Binary files /dev/null and b/scripts/system/create/audioFeedback/sounds/rejection.mp3 differ diff --git a/scripts/system/create/edit.js b/scripts/system/create/edit.js index b8f1dc8014..5c488a71ee 100644 --- a/scripts/system/create/edit.js +++ b/scripts/system/create/edit.js @@ -34,7 +34,8 @@ Script.include([ "../libraries/entityIconOverlayManager.js", "../libraries/gridTool.js", "entityList/entityList.js", - "entitySelectionTool/entitySelectionTool.js" + "entitySelectionTool/entitySelectionTool.js", + "audioFeedback/audioFeedback.js" ]); var CreateWindow = Script.require('./modules/createWindow.js'); @@ -104,6 +105,8 @@ var entityIconOverlayManager = new EntityIconOverlayManager(['Light', 'ParticleE } }); +var hmdMultiSelectMode = false; + var cameraManager = new CameraManager(); var grid = new Grid(); @@ -824,7 +827,7 @@ var toolBar = (function () { HMD.displayModeChanged.connect(function() { if (isActive) { - tablet.gotoHomeScreen(); + tablet.gotoHomeScreen(); } that.setActive(false); }); @@ -1131,7 +1134,11 @@ function handleOverlaySelectionToolUpdates(channel, message, sender) { var entity = entityIconOverlayManager.findEntity(data.overlayID); if (entity !== null) { - selectionManager.setSelections([entity], this); + if (hmdMultiSelectMode) { + selectionManager.addEntity(entity, true, this); + } else { + selectionManager.setSelections([entity], this); + } } } } @@ -1694,6 +1701,7 @@ function unparentSelectedEntities() { var parentCheck = false; if (selectedEntities.length < 1) { + audioFeedback.rejection(); Window.notifyEditError("You must have an entity selected in order to unparent it."); return; } @@ -1706,12 +1714,14 @@ function unparentSelectedEntities() { return true; }); if (parentCheck) { + audioFeedback.confirmation(); if (selectedEntities.length > 1) { Window.notify("Entities unparented"); } else { Window.notify("Entity unparented"); } } else { + audioFeedback.rejection(); if (selectedEntities.length > 1) { Window.notify("Selected Entities have no parents"); } else { @@ -1719,6 +1729,7 @@ function unparentSelectedEntities() { } } } else { + audioFeedback.rejection(); Window.notifyEditError("You have nothing selected to unparent"); } } @@ -1726,6 +1737,7 @@ function parentSelectedEntities() { if (SelectionManager.hasSelection()) { var selectedEntities = selectionManager.selections; if (selectedEntities.length <= 1) { + audioFeedback.rejection(); Window.notifyEditError("You must have multiple entities selected in order to parent them"); return; } @@ -1742,11 +1754,14 @@ function parentSelectedEntities() { }); if (parentCheck) { + audioFeedback.confirmation(); Window.notify("Entities parented"); } else { + audioFeedback.rejection(); Window.notify("Entities are already parented to last"); } } else { + audioFeedback.rejection(); Window.notifyEditError("You have nothing selected to parent"); } } @@ -2339,6 +2354,15 @@ var PropertiesTool = function (opts) { }; function updateSelections(selectionUpdated, caller) { + if (HMD.active && visible) { + webView.setLandscape(true); + } else { + if (!visible) { + hmdMultiSelectMode = false; + webView.setLandscape(false); + } + } + if (blockPropertyUpdates) { return; } diff --git a/scripts/system/create/entityList/entityList.js b/scripts/system/create/entityList/entityList.js index b68dcf80ba..4b8163968d 100644 --- a/scripts/system/create/entityList/entityList.js +++ b/scripts/system/create/entityList/entityList.js @@ -3,6 +3,7 @@ // entityList.js // // Copyright 2014 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -14,6 +15,7 @@ var PROFILING_ENABLED = false; var profileIndent = ''; + const PROFILE_NOOP = function(_name, fn, args) { fn.apply(this, args); }; @@ -73,7 +75,7 @@ EntityListTool = function(shouldUseEditTabletApp) { that.setVisible = function(newVisible) { visible = newVisible; webView.setVisible(shouldUseEditTabletApp() && visible); - entityListWindow.setVisible(!shouldUseEditTabletApp() && visible); + entityListWindow.setVisible(!shouldUseEditTabletApp() && visible); }; that.isVisible = function() { @@ -163,6 +165,15 @@ EntityListTool = function(shouldUseEditTabletApp) { } that.sendUpdate = function() { + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + if (HMD.active) { + tablet.setLandscape(true); + } + emitJSONScriptEvent({ + "type": "confirmHMDstate", + "isHmd": HMD.active + }); + PROFILE('Script-sendUpdate', function() { var entities = []; @@ -302,6 +313,16 @@ EntityListTool = function(shouldUseEditTabletApp) { SelectionDisplay.toggleSpaceMode(); } else if (data.type === 'keyUpEvent') { keyUpEventFromUIWindow(data.keyUpEvent); + } else if (data.type === 'undo') { + undoHistory.undo(); + } else if (data.type === 'redo') { + undoHistory.redo(); + } else if (data.type === 'parent') { + parentSelectedEntities(); + } else if (data.type === 'unparent') { + unparentSelectedEntities(); + } else if (data.type === 'hmdMultiSelectMode') { + hmdMultiSelectMode = data.value; } }; diff --git a/scripts/system/create/entityList/html/entityList.html b/scripts/system/create/entityList/html/entityList.html index b7ff7cd4e4..824ca380ec 100644 --- a/scripts/system/create/entityList/html/entityList.html +++ b/scripts/system/create/entityList/html/entityList.html @@ -3,6 +3,7 @@ // // Created by Ryan Huffman on 19 Nov 2014 // Copyright 2014 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -30,6 +31,19 @@ + + + + + +
+ + +
+
+ + +
diff --git a/scripts/system/create/entityList/html/js/entityList.js b/scripts/system/create/entityList/html/js/entityList.js index b70e53ce15..aa40d5286f 100644 --- a/scripts/system/create/entityList/html/js/entityList.js +++ b/scripts/system/create/entityList/html/js/entityList.js @@ -2,6 +2,7 @@ // // Created by Ryan Huffman on 19 Nov 2014 // Copyright 2014 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -164,6 +165,7 @@ let selectedEntities = []; let entityList = null; // The ListView +let hmdMultiSelectMode = false; /** * @type EntityListContextMenu */ @@ -198,6 +200,15 @@ let elEntityTable, elRefresh, elToggleLocked, elToggleVisible, + elHmdMultiSelect, + elHmdCopy, + elHmdCut, + elHmdPaste, + elHmdDuplicate, + elUndo, + elRedo, + elParent, + elUnparent, elDelete, elFilterTypeMultiselectBox, elFilterTypeText, @@ -233,7 +244,7 @@ const PROFILE = !ENABLE_PROFILING ? PROFILE_NOOP : function(name, fn, args) { console.log("PROFILE-Web " + profileIndent + "(" + name + ") End " + delta + "ms"); }; -function loaded() { +function loaded() { openEventBridge(function() { elEntityTable = document.getElementById("entity-table"); elEntityTableHeader = document.getElementById("entity-table-header"); @@ -242,6 +253,15 @@ function loaded() { elRefresh = document.getElementById("refresh"); elToggleLocked = document.getElementById("locked"); elToggleVisible = document.getElementById("visible"); + elHmdMultiSelect = document.getElementById("hmdmultiselect"); + elHmdCopy = document.getElementById("hmdcopy"); + elHmdCut = document.getElementById("hmdcut"); + elHmdPaste = document.getElementById("hmdpaste"); + elHmdDuplicate = document.getElementById("hmdduplicate"); + elUndo = document.getElementById("undo"); + elRedo = document.getElementById("redo"); + elParent = document.getElementById("parent"); + elUnparent = document.getElementById("unparent"); elDelete = document.getElementById("delete"); elFilterTypeMultiselectBox = document.getElementById("filter-type-multiselect-box"); elFilterTypeText = document.getElementById("filter-type-text"); @@ -270,6 +290,40 @@ function loaded() { elExport.onclick = function() { EventBridge.emitWebEvent(JSON.stringify({ type: 'export'})); }; + elHmdMultiSelect.onclick = function() { + if (hmdMultiSelectMode) { + elHmdMultiSelect.className = "vglyph"; + hmdMultiSelectMode = false; + } else { + elHmdMultiSelect.className = "white vglyph"; + hmdMultiSelectMode = true; + } + EventBridge.emitWebEvent(JSON.stringify({ type: 'hmdMultiSelectMode', value: hmdMultiSelectMode })); + }; + elHmdCopy.onclick = function() { + EventBridge.emitWebEvent(JSON.stringify({ type: 'copy' })); + }; + elHmdCut.onclick = function() { + EventBridge.emitWebEvent(JSON.stringify({ type: 'cut' })); + }; + elHmdPaste.onclick = function() { + EventBridge.emitWebEvent(JSON.stringify({ type: 'paste' })); + }; + elHmdDuplicate.onclick = function() { + EventBridge.emitWebEvent(JSON.stringify({ type: 'duplicate' })); + }; + elParent.onclick = function() { + EventBridge.emitWebEvent(JSON.stringify({ type: 'parent' })); + }; + elUnparent.onclick = function() { + EventBridge.emitWebEvent(JSON.stringify({ type: 'unparent' })); + }; + elUndo.onclick = function() { + EventBridge.emitWebEvent(JSON.stringify({ type: 'undo' })); + }; + elRedo.onclick = function() { + EventBridge.emitWebEvent(JSON.stringify({ type: 'redo' })); + }; elDelete.onclick = function() { EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' })); }; @@ -538,7 +592,7 @@ function loaded() { let selection = [entityID]; let controlKey = window.navigator.platform.startsWith("Mac") ? clickEvent.metaKey : clickEvent.ctrlKey; - if (controlKey) { + if (controlKey || hmdMultiSelectMode) { let selectedIndex = selectedEntities.indexOf(entityID); if (selectedIndex >= 0) { selection = []; @@ -1364,10 +1418,12 @@ function loaded() { } })); }, false); - + if (window.EventBridge !== undefined) { EventBridge.scriptEventReceived.connect(function(data) { + data = JSON.parse(data); + if (data.type === "clearEntityList") { clearEntities(); } else if (data.type === "selectionUpdate") { @@ -1395,6 +1451,20 @@ function loaded() { removeEntities(data.ids); } else if (data.type === "setSpaceMode") { setSpaceMode(data.spaceMode); + } else if (data.type === "confirmHMDstate") { + if (data.isHmd) { + document.getElementById("hmdmultiselect").style.display = "inline"; + document.getElementById("hmdcopy").style.display = "inline"; + document.getElementById("hmdcut").style.display = "inline"; + document.getElementById("hmdpaste").style.display = "inline"; + document.getElementById("hmdduplicate").style.display = "inline"; + } else { + document.getElementById("hmdmultiselect").style.display = "none"; + document.getElementById("hmdcopy").style.display = "none"; + document.getElementById("hmdcut").style.display = "none"; + document.getElementById("hmdpaste").style.display = "none"; + document.getElementById("hmdduplicate").style.display = "none"; + } } }); } diff --git a/scripts/system/create/entitySelectionTool/entitySelectionTool.js b/scripts/system/create/entitySelectionTool/entitySelectionTool.js index 33f9bbafb3..0250ead0a9 100644 --- a/scripts/system/create/entitySelectionTool/entitySelectionTool.js +++ b/scripts/system/create/entitySelectionTool/entitySelectionTool.js @@ -103,7 +103,11 @@ SelectionManager = (function() { if (wantDebug) { print("setting selection to " + messageParsed.entityID); } - that.setSelections([messageParsed.entityID], that); + if (hmdMultiSelectMode) { + that.addEntity(messageParsed.entityID, true, that); + } else { + that.setSelections([messageParsed.entityID], that); + } } } else if (messageParsed.method === "clearSelection") { if (!SelectionDisplay.triggered() || SelectionDisplay.triggeredHand === messageParsed.hand) { @@ -314,6 +318,7 @@ SelectionManager = (function() { that.addChildrenEntities(originalEntityID, entitiesToDuplicate, entityHostTypes[i].entityHostType); } + var duplicateInterrupted = false; // duplicate entities from above and store their original to new entity mappings and children needing re-parenting for (var i = 0; i < entitiesToDuplicate.length; i++) { var originalEntityID = entitiesToDuplicate[i]; @@ -360,6 +365,8 @@ SelectionManager = (function() { duplicatedChildrenWithOldParents[newEntityID] = properties.parentID; } originalEntityToNewEntityID[originalEntityID] = newEntityID; + } else { + duplicateInterrupted = true; } } @@ -378,6 +385,11 @@ SelectionManager = (function() { } }); + if (duplicateInterrupted) { + audioFeedback.rejection(); + } else { + audioFeedback.confirmation(); + } return duplicatedEntityIDs; }; diff --git a/scripts/system/create/qml/EditTabView.qml b/scripts/system/create/qml/EditTabView.qml index a0cff70d50..53f6068424 100644 --- a/scripts/system/create/qml/EditTabView.qml +++ b/scripts/system/create/qml/EditTabView.qml @@ -55,18 +55,18 @@ TabBar { font.pixelSize: 14 font.bold: true anchors.top: parent.top - anchors.topMargin: 28 + anchors.topMargin: 30 anchors.left: parent.left - anchors.leftMargin: 28 + anchors.leftMargin: 30 } Flow { id: createEntitiesFlow - spacing: 35 + spacing: 20 anchors.right: parent.right - anchors.rightMargin: 55 + anchors.rightMargin: 30 anchors.left: parent.left - anchors.leftMargin: 55 + anchors.leftMargin: 30 anchors.top: parent.top anchors.topMargin: 70 @@ -186,9 +186,9 @@ TabBar { color: hifi.buttons.black colorScheme: hifi.colorSchemes.dark anchors.right: parent.right - anchors.rightMargin: 55 + anchors.rightMargin: 30 anchors.left: parent.left - anchors.leftMargin: 55 + anchors.leftMargin: 30 anchors.top: createEntitiesFlow.bottom anchors.topMargin: 35 onClicked: { @@ -205,9 +205,9 @@ TabBar { color: hifi.buttons.black colorScheme: hifi.colorSchemes.dark anchors.right: parent.right - anchors.rightMargin: 55 + anchors.rightMargin: 30 anchors.left: parent.left - anchors.leftMargin: 55 + anchors.leftMargin: 30 anchors.top: assetServerButton.bottom anchors.topMargin: 20 onClicked: { diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index ada8116a0d..8a381ff4ad 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -65,6 +65,14 @@ url(../fonts/hifi-glyphs.ttf); } +@font-face { + font-family: Vircadia-Glyphs; + src: url(../../../../resources/fonts/vircadia_glyphs.ttf), + url(../../../../fonts/vircadia_glyphs.ttf), + url(../../../../interface/resources/fonts/vircadia_glyphs.ttf), + url(../fonts/vircadia_glyphs.ttf); +} + * { margin: 0; padding: 0; @@ -407,6 +415,14 @@ input[type=button].glyph, button.hifi-edit-button.glyph { padding: 0; } +input[type=button].vglyph, button.hifi-edit-button.vglyph { + font-family: Vircadia-Glyphs; + font-size: 20px; + text-transform: none; + min-width: 32px; + padding: 0; +} + input[type=button].red, button.hifi-edit-button.red { color: #fff; background-color: #94132e; @@ -417,6 +433,16 @@ input[type=button].blue, button.hifi-edit-button.blue { background-color: #1080b8; background: linear-gradient(#00b4ef 20%, #1080b8 100%); } +input[type=button].orange, button.hifi-edit-button.orange { + color: #fff; + background-color: #8f5100; + background: linear-gradient(#d97b00 20%, #8f5100 100%); +} +input[type=button].green, button.hifi-edit-button.green { + color: #fff; + background-color: #078a00; + background: linear-gradient(#00cc07 20%, #078a00 100%); +} input[type=button].white, button.hifi-edit-button.white { color: #121212; background-color: #afafaf; @@ -435,6 +461,14 @@ input[type=button].blue:enabled:hover, button.hifi-edit-button.blue:enabled:hove background: linear-gradient(#00b4ef, #00b4ef); border: none; } +input[type=button].orange:enabled:hover, button.hifi-edit-button.orange:enabled:hover { + background: linear-gradient(#d97b00, #d97b00); + border: none; +} +input[type=button].green:enabled:hover, button.hifi-edit-button.green:enabled:hover { + background: linear-gradient(#00cc07, #00cc07); + border: none; +} input[type=button].white:enabled:hover, button.hifi-edit-button.white:enabled:hover { background: linear-gradient(#fff, #fff); border: none; @@ -449,6 +483,12 @@ input[type=button].red:active, button.hifi-edit-button.red:active { input[type=button].blue:active, button.hifi-edit-button.blue:active { background: linear-gradient(#1080b8, #1080b8); } +input[type=button].orange:active, button.hifi-edit-button.orange:active { + background: linear-gradient(#8f5100, #8f5100); +} +input[type=button].green:active, button.hifi-edit-button.green:active { + background: linear-gradient(#078a00, #078a00); +} input[type=button].white:active, button.hifi-edit-button.white:active { background: linear-gradient(#afafaf, #afafaf); }