diff --git a/interface/resources/qml/hifi/tablet/Edit.qml b/interface/resources/qml/hifi/tablet/Edit.qml index 4abe698fbc..ea31eb26d8 100644 --- a/interface/resources/qml/hifi/tablet/Edit.qml +++ b/interface/resources/qml/hifi/tablet/Edit.qml @@ -1,19 +1,11 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 -import QtWebEngine 1.1 -import QtWebChannel 1.0 -import QtQuick.Controls.Styles 1.4 -import "../../controls" -import "../toolbars" -import HFWebEngineProfile 1.0 -import QtGraphicalEffects 1.0 -import "../../controls-uit" as HifiControls import "../../styles-uit" StackView { id: editRoot objectName: "stack" - initialItem: editBasePage + initialItem: Qt.resolvedUrl('EditTabView.qml') property var eventBridge; signal sendToScript(var message); @@ -30,270 +22,10 @@ StackView { editRoot.pop(); } - - Component { - id: editBasePage - TabView { - id: editTabView - // anchors.fill: parent - height: 60 - - Tab { - title: "CREATE" - active: true - enabled: true - property string originalUrl: "" - - Rectangle { - color: "#404040" - - Text { - color: "#ffffff" - text: "Choose an Entity Type to Create:" - font.pixelSize: 14 - font.bold: true - anchors.top: parent.top - anchors.topMargin: 28 - anchors.left: parent.left - anchors.leftMargin: 28 - } - - Flow { - id: createEntitiesFlow - spacing: 35 - anchors.right: parent.right - anchors.rightMargin: 55 - anchors.left: parent.left - anchors.leftMargin: 55 - anchors.top: parent.top - anchors.topMargin: 70 - - - NewEntityButton { - icon: "icons/create-icons/94-model-01.svg" - text: "MODEL" - onClicked: { - editRoot.sendToScript({ - method: "newEntityButtonClicked", params: { buttonName: "newModelButton" } - }); - editTabView.currentIndex = 2 - } - } - - NewEntityButton { - icon: "icons/create-icons/21-cube-01.svg" - text: "CUBE" - onClicked: { - editRoot.sendToScript({ - method: "newEntityButtonClicked", params: { buttonName: "newCubeButton" } - }); - editTabView.currentIndex = 2 - } - } - - NewEntityButton { - icon: "icons/create-icons/22-sphere-01.svg" - text: "SPHERE" - onClicked: { - editRoot.sendToScript({ - method: "newEntityButtonClicked", params: { buttonName: "newSphereButton" } - }); - editTabView.currentIndex = 2 - } - } - - NewEntityButton { - icon: "icons/create-icons/24-light-01.svg" - text: "LIGHT" - onClicked: { - editRoot.sendToScript({ - method: "newEntityButtonClicked", params: { buttonName: "newLightButton" } - }); - editTabView.currentIndex = 2 - } - } - - NewEntityButton { - icon: "icons/create-icons/20-text-01.svg" - text: "TEXT" - onClicked: { - editRoot.sendToScript({ - method: "newEntityButtonClicked", params: { buttonName: "newTextButton" } - }); - editTabView.currentIndex = 2 - } - } - - NewEntityButton { - icon: "icons/create-icons/25-web-1-01.svg" - text: "WEB" - onClicked: { - editRoot.sendToScript({ - method: "newEntityButtonClicked", params: { buttonName: "newWebButton" } - }); - editTabView.currentIndex = 2 - } - } - - NewEntityButton { - icon: "icons/create-icons/23-zone-01.svg" - text: "ZONE" - onClicked: { - editRoot.sendToScript({ - method: "newEntityButtonClicked", params: { buttonName: "newZoneButton" } - }); - editTabView.currentIndex = 2 - } - } - - NewEntityButton { - icon: "icons/create-icons/90-particles-01.svg" - text: "PARTICLE" - onClicked: { - editRoot.sendToScript({ - method: "newEntityButtonClicked", params: { buttonName: "newParticleButton" } - }); - editTabView.currentIndex = 2 - } - } - } - - HifiControls.Button { - id: assetServerButton - text: "Open This Domain's Asset Server" - color: hifi.buttons.black - colorScheme: hifi.colorSchemes.dark - anchors.right: parent.right - anchors.rightMargin: 55 - anchors.left: parent.left - anchors.leftMargin: 55 - anchors.top: createEntitiesFlow.bottom - anchors.topMargin: 35 - onClicked: { - editRoot.sendToScript({ - method: "newEntityButtonClicked", params: { buttonName: "openAssetBrowserButton" } - }); - } - } - - HifiControls.Button { - text: "Import Entities (.json)" - color: hifi.buttons.black - colorScheme: hifi.colorSchemes.dark - anchors.right: parent.right - anchors.rightMargin: 55 - anchors.left: parent.left - anchors.leftMargin: 55 - anchors.top: assetServerButton.bottom - anchors.topMargin: 20 - onClicked: { - editRoot.sendToScript({ - method: "newEntityButtonClicked", params: { buttonName: "importEntitiesButton" } - }); - } - } - } - } - - Tab { - title: "LIST" - active: true - enabled: true - property string originalUrl: "" - - WebView { - id: entityListToolWebView - url: "../../../../../scripts/system/html/entityList.html" - eventBridge: editRoot.eventBridge - anchors.fill: parent - enabled: true - } - } - - Tab { - title: "PROPERTIES" - active: true - enabled: true - property string originalUrl: "" - - WebView { - id: entityPropertiesWebView - url: "../../../../../scripts/system/html/entityProperties.html" - eventBridge: editRoot.eventBridge - anchors.fill: parent - enabled: true - } - } - - Tab { - title: "GRID" - active: true - enabled: true - property string originalUrl: "" - - WebView { - id: gridControlsWebView - url: "../../../../../scripts/system/html/gridControls.html" - eventBridge: editRoot.eventBridge - anchors.fill: parent - enabled: true - } - } - - Tab { - title: "P" - active: true - enabled: true - property string originalUrl: "" - - WebView { - id: particleExplorerWebView - url: "../../../../../scripts/system/particle_explorer/particleExplorer.html" - eventBridge: editRoot.eventBridge - anchors.fill: parent - enabled: true - } - } - - - style: TabViewStyle { - frameOverlap: 1 - tab: Rectangle { - color: styleData.selected ? "#404040" :"black" - implicitWidth: text.width + 42 - implicitHeight: 40 - Text { - id: text - anchors.centerIn: parent - text: styleData.title - font.pixelSize: 16 - font.bold: true - color: styleData.selected ? "white" : "white" - property string glyphtext: "" - HiFiGlyphs { - anchors.centerIn: parent - size: 30 - color: "#ffffff" - text: text.glyphtext - } - Component.onCompleted: if (styleData.title == "P") { - text.text = " "; - text.glyphtext = "\ue004"; - } - } - } - tabBar: Rectangle { - color: "black" - anchors.right: parent.right - anchors.rightMargin: 0 - anchors.left: parent.left - anchors.leftMargin: 0 - anchors.bottom: parent.bottom - anchors.bottomMargin: 0 - anchors.top: parent.top - anchors.topMargin: 0 - } - } - } + // Passes script messages to the item on the top of the stack + function fromScript(message) { + var currentItem = editRoot.currentItem; + if (currentItem && currentItem.fromScript) + currentItem.fromScript(message); } } diff --git a/interface/resources/qml/hifi/tablet/EditTabView.qml b/interface/resources/qml/hifi/tablet/EditTabView.qml new file mode 100644 index 0000000000..35f2b82f0f --- /dev/null +++ b/interface/resources/qml/hifi/tablet/EditTabView.qml @@ -0,0 +1,318 @@ +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtWebEngine 1.1 +import QtWebChannel 1.0 +import QtQuick.Controls.Styles 1.4 +import "../../controls" +import "../toolbars" +import HFWebEngineProfile 1.0 +import QtGraphicalEffects 1.0 +import "../../controls-uit" as HifiControls +import "../../styles-uit" + + +TabView { + id: editTabView + // anchors.fill: parent + height: 60 + + Tab { + title: "CREATE" + active: true + enabled: true + property string originalUrl: "" + + Rectangle { + color: "#404040" + + Text { + color: "#ffffff" + text: "Choose an Entity Type to Create:" + font.pixelSize: 14 + font.bold: true + anchors.top: parent.top + anchors.topMargin: 28 + anchors.left: parent.left + anchors.leftMargin: 28 + } + + Flow { + id: createEntitiesFlow + spacing: 35 + anchors.right: parent.right + anchors.rightMargin: 55 + anchors.left: parent.left + anchors.leftMargin: 55 + anchors.top: parent.top + anchors.topMargin: 70 + + + NewEntityButton { + icon: "icons/create-icons/94-model-01.svg" + text: "MODEL" + onClicked: { + editRoot.sendToScript({ + method: "newEntityButtonClicked", params: { buttonName: "newModelButton" } + }); + editTabView.currentIndex = 2 + } + } + + NewEntityButton { + icon: "icons/create-icons/21-cube-01.svg" + text: "CUBE" + onClicked: { + editRoot.sendToScript({ + method: "newEntityButtonClicked", params: { buttonName: "newCubeButton" } + }); + editTabView.currentIndex = 2 + } + } + + NewEntityButton { + icon: "icons/create-icons/22-sphere-01.svg" + text: "SPHERE" + onClicked: { + editRoot.sendToScript({ + method: "newEntityButtonClicked", params: { buttonName: "newSphereButton" } + }); + editTabView.currentIndex = 2 + } + } + + NewEntityButton { + icon: "icons/create-icons/24-light-01.svg" + text: "LIGHT" + onClicked: { + editRoot.sendToScript({ + method: "newEntityButtonClicked", params: { buttonName: "newLightButton" } + }); + editTabView.currentIndex = 2 + } + } + + NewEntityButton { + icon: "icons/create-icons/20-text-01.svg" + text: "TEXT" + onClicked: { + editRoot.sendToScript({ + method: "newEntityButtonClicked", params: { buttonName: "newTextButton" } + }); + editTabView.currentIndex = 2 + } + } + + NewEntityButton { + icon: "icons/create-icons/25-web-1-01.svg" + text: "WEB" + onClicked: { + editRoot.sendToScript({ + method: "newEntityButtonClicked", params: { buttonName: "newWebButton" } + }); + editTabView.currentIndex = 2 + } + } + + NewEntityButton { + icon: "icons/create-icons/23-zone-01.svg" + text: "ZONE" + onClicked: { + editRoot.sendToScript({ + method: "newEntityButtonClicked", params: { buttonName: "newZoneButton" } + }); + editTabView.currentIndex = 2 + } + } + + NewEntityButton { + icon: "icons/create-icons/90-particles-01.svg" + text: "PARTICLE" + onClicked: { + editRoot.sendToScript({ + method: "newEntityButtonClicked", params: { buttonName: "newParticleButton" } + }); + editTabView.currentIndex = 4 + } + } + } + + HifiControls.Button { + id: assetServerButton + text: "Open This Domain's Asset Server" + color: hifi.buttons.black + colorScheme: hifi.colorSchemes.dark + anchors.right: parent.right + anchors.rightMargin: 55 + anchors.left: parent.left + anchors.leftMargin: 55 + anchors.top: createEntitiesFlow.bottom + anchors.topMargin: 35 + onClicked: { + editRoot.sendToScript({ + method: "newEntityButtonClicked", params: { buttonName: "openAssetBrowserButton" } + }); + } + } + + HifiControls.Button { + text: "Import Entities (.json)" + color: hifi.buttons.black + colorScheme: hifi.colorSchemes.dark + anchors.right: parent.right + anchors.rightMargin: 55 + anchors.left: parent.left + anchors.leftMargin: 55 + anchors.top: assetServerButton.bottom + anchors.topMargin: 20 + onClicked: { + editRoot.sendToScript({ + method: "newEntityButtonClicked", params: { buttonName: "importEntitiesButton" } + }); + } + } + } + } + + Tab { + title: "LIST" + active: true + enabled: true + property string originalUrl: "" + + WebView { + id: entityListToolWebView + url: "../../../../../scripts/system/html/entityList.html" + eventBridge: editRoot.eventBridge + anchors.fill: parent + enabled: true + } + } + + Tab { + title: "PROPERTIES" + active: true + enabled: true + property string originalUrl: "" + + WebView { + id: entityPropertiesWebView + url: "../../../../../scripts/system/html/entityProperties.html" + eventBridge: editRoot.eventBridge + anchors.fill: parent + enabled: true + } + } + + Tab { + title: "GRID" + active: true + enabled: true + property string originalUrl: "" + + WebView { + id: gridControlsWebView + url: "../../../../../scripts/system/html/gridControls.html" + eventBridge: editRoot.eventBridge + anchors.fill: parent + enabled: true + } + } + + Tab { + title: "P" + active: true + enabled: true + property string originalUrl: "" + + WebView { + id: particleExplorerWebView + url: "../../../../../scripts/system/particle_explorer/particleExplorer.html" + eventBridge: editRoot.eventBridge + anchors.fill: parent + enabled: true + } + } + + + style: TabViewStyle { + frameOverlap: 1 + tab: Rectangle { + color: styleData.selected ? "#404040" :"black" + implicitWidth: text.width + 42 + implicitHeight: 40 + Text { + id: text + anchors.centerIn: parent + text: styleData.title + font.pixelSize: 16 + font.bold: true + color: styleData.selected ? "white" : "white" + property string glyphtext: "" + HiFiGlyphs { + anchors.centerIn: parent + size: 30 + color: "#ffffff" + text: text.glyphtext + } + Component.onCompleted: if (styleData.title == "P") { + text.text = " "; + text.glyphtext = "\ue004"; + } + } + } + tabBar: Rectangle { + color: "black" + anchors.right: parent.right + anchors.rightMargin: 0 + anchors.left: parent.left + anchors.leftMargin: 0 + anchors.bottom: parent.bottom + anchors.bottomMargin: 0 + anchors.top: parent.top + anchors.topMargin: 0 + } + } + + function fromScript(message) { + switch (message.method) { + case 'selectTab': + selectTab(message.params.id); + break; + default: + console.warn('Unrecognized message:', JSON.stringify(message)); + } + } + + // Changes the current tab based on tab index or title as input + function selectTab(id) { + if (typeof id === 'number') { + if (id >= 0 && id <= 4) { + editTabView.currentIndex = id; + } else { + console.warn('Attempt to switch to invalid tab:', id); + } + } else if (typeof id === 'string'){ + switch (id.toLowerCase()) { + case 'create': + editTabView.currentIndex = 0; + break; + case 'list': + editTabView.currentIndex = 1; + break; + case 'properties': + editTabView.currentIndex = 2; + break; + case 'grid': + editTabView.currentIndex = 3; + break; + case 'particle': + editTabView.currentIndex = 4; + break; + default: + console.warn('Attempt to switch to invalid tab:', id); + } + } else { + console.warn('Attempt to switch tabs with invalid input:', JSON.stringify(id)); + } + } +} \ No newline at end of file diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index f1a2e7bd08..026a382e58 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -1330,7 +1330,7 @@ function MyController(hand) { if (this.stylus) { return; } - + var stylusProperties = { name: "stylus", url: Script.resourcesPath() + "meshes/tablet-stylus-fat.fbx", @@ -1420,6 +1420,14 @@ function MyController(hand) { } }; + // Turns off indicators used for searching. Overlay line and sphere. + this.searchIndicatorOff = function() { + this.searchSphereOff(); + if (PICK_WITH_HAND_RAY) { + this.overlayLineOff(); + } + } + this.otherGrabbingLineOn = function(avatarPosition, entityPosition, color) { if (this.otherGrabbingLine === null) { var lineProperties = { @@ -1791,6 +1799,15 @@ function MyController(hand) { } this.processStylus(); + + if (isInEditMode() && !this.isNearStylusTarget) { + // Always showing lasers while in edit mode and hands/stylus is not active. + var rayPickInfo = this.calcRayPickInfo(this.hand); + this.intersectionDistance = (rayPickInfo.entityID || rayPickInfo.overlayID) ? rayPickInfo.distance : 0; + this.searchIndicatorOn(rayPickInfo.searchRay); + } else { + this.searchIndicatorOff(); + } }; this.handleLaserOnHomeButton = function(rayPickInfo) { @@ -2237,15 +2254,22 @@ function MyController(hand) { return; } } - + if (isInEditMode()) { this.searchIndicatorOn(rayPickInfo.searchRay); if (this.triggerSmoothedGrab()) { - if (!this.editTriggered && rayPickInfo.entityID) { - Messages.sendLocalMessage("entityToolUpdates", JSON.stringify({ - method: "selectEntity", - entityID: rayPickInfo.entityID - })); + if (!this.editTriggered){ + if (rayPickInfo.entityID) { + Messages.sendLocalMessage("entityToolUpdates", JSON.stringify({ + method: "selectEntity", + entityID: rayPickInfo.entityID + })); + } else if (rayPickInfo.overlayID) { + Messages.sendLocalMessage("entityToolUpdates", JSON.stringify({ + method: "selectOverlay", + overlayID: rayPickInfo.overlayID + })); + } } this.editTriggered = true; } @@ -2274,7 +2298,7 @@ function MyController(hand) { if (this.getOtherHandController().state === STATE_DISTANCE_HOLDING) { this.setState(STATE_DISTANCE_ROTATING, "distance rotate '" + name + "'"); } else { - this.setState(STATE_DISTANCE_HOLDING, "distance hold '" + name + "'"); + this.setState(STATE_DISTANCE_HOLDING, "distance hold '" + name + "'"); } return; } else { @@ -3341,7 +3365,14 @@ function MyController(hand) { }; this.offEnter = function() { + // Reuse the existing search distance if lasers were active since + // they will be shown in OFF state while in edit mode. + var existingSearchDistance = this.searchSphereDistance; this.release(); + + if (isInEditMode()) { + this.searchSphereDistance = existingSearchDistance; + } }; this.entityLaserTouchingEnter = function() { diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 0918d553df..6fabeb2ec6 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -12,7 +12,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/* global Script, SelectionDisplay, LightOverlayManager, CameraManager, Grid, GridTool, EntityListTool, Vec3, SelectionManager, Overlays, OverlayWebWindow, UserActivityLogger, Settings, Entities, Tablet, Toolbars, Messages, Menu, Camera, progressDialog, tooltip, MyAvatar, Quat, Controller, Clipboard, HMD, UndoStack, ParticleExplorerTool */ +/* global Script, SelectionDisplay, LightOverlayManager, CameraManager, Grid, GridTool, EntityListTool, Vec3, SelectionManager, Overlays, OverlayWebWindow, UserActivityLogger, + Settings, Entities, Tablet, Toolbars, Messages, Menu, Camera, progressDialog, tooltip, MyAvatar, Quat, Controller, Clipboard, HMD, UndoStack, ParticleExplorerTool */ (function() { // BEGIN LOCAL_SCOPE @@ -96,6 +97,10 @@ selectionManager.addEventListener(function () { particleExplorerTool.webView.emitScriptEvent(JSON.stringify(particleData)); } }); + + // Switch to particle explorer + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + tablet.sendToQml({method: 'selectTab', params: {id: 'particle'}}); } else { needToDestroyParticleExplorer = true; } @@ -644,6 +649,27 @@ function findClickedEntity(event) { }; } +// Handles selections on overlays while in edit mode by querying entities from +// entityIconOverlayManager. +function handleOverlaySelectionToolUpdates(channel, message, sender) { + if (sender !== MyAvatar.sessionUUID || channel !== 'entityToolUpdates') + return; + + var data = JSON.parse(message); + + if (data.method === "selectOverlay") { + print("setting selection to overlay " + data.overlayID); + var entity = entityIconOverlayManager.findEntity(data.overlayID); + + if (entity !== null) { + selectionManager.setSelections([entity]); + } + } +} + +Messages.subscribe("entityToolUpdates"); +Messages.messageReceived.connect(handleOverlaySelectionToolUpdates); + var mouseHasMovedSincePress = false; var mousePressStartTime = 0; var mousePressStartPosition = { @@ -1063,6 +1089,13 @@ Script.scriptEnding.connect(function () { Controller.keyReleaseEvent.disconnect(keyReleaseEvent); Controller.keyPressEvent.disconnect(keyPressEvent); + + Controller.mousePressEvent.disconnect(mousePressEvent); + Controller.mouseMoveEvent.disconnect(mouseMoveEventBuffered); + Controller.mouseReleaseEvent.disconnect(mouseReleaseEvent); + + Messages.messageReceived.disconnect(handleOverlaySelectionToolUpdates); + Messages.unsubscribe("entityToolUpdates"); }); var lastOrientation = null; @@ -2029,7 +2062,11 @@ function selectParticleEntity(entityID) { selectedParticleEntity = entityID; particleExplorerTool.setActiveParticleEntity(entityID); - particleExplorerTool.webView.emitScriptEvent(JSON.stringify(particleData)); + particleExplorerTool.webView.emitScriptEvent(JSON.stringify(particleData)); + + // Switch to particle explorer + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + tablet.sendToQml({method: 'selectTab', params: {id: 'particle'}}); } entityListTool.webView.webEventReceived.connect(function (data) { diff --git a/scripts/system/libraries/entityIconOverlayManager.js b/scripts/system/libraries/entityIconOverlayManager.js index 7f7a293bc3..f557a05f60 100644 --- a/scripts/system/libraries/entityIconOverlayManager.js +++ b/scripts/system/libraries/entityIconOverlayManager.js @@ -32,20 +32,25 @@ EntityIconOverlayManager = function(entityTypes, getOverlayPropertiesFunc) { } }; + // Finds the id for the corresponding entity that is associated with an overlay id. + // Returns null if the overlay id is not contained in this manager. + this.findEntity = function(overlayId) { + for (var id in entityOverlays) { + if (overlayId === entityOverlays[id]) { + return entityIDs[id]; + } + } + + return null; + }; + this.findRayIntersection = function(pickRay) { var result = Overlays.findRayIntersection(pickRay); - var found = false; if (result.intersects) { - for (var id in entityOverlays) { - if (result.overlayID === entityOverlays[id]) { - result.entityID = entityIDs[id]; - found = true; - break; - } - } + result.entityID = this.findEntity(result.overlayID); - if (!found) { + if (result.entityID === null) { result.intersects = false; } } diff --git a/scripts/system/particle_explorer/particleExplorer.html b/scripts/system/particle_explorer/particleExplorer.html index d12ceac14b..d0d86d79da 100644 --- a/scripts/system/particle_explorer/particleExplorer.html +++ b/scripts/system/particle_explorer/particleExplorer.html @@ -14,10 +14,13 @@ --> + + +