// // Gestures.qml // qml/hifi // // Gestures App // // Created by Zach Fox on 2018-07-24 // 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 // import Hifi 1.0 as Hifi import QtQuick 2.7 import QtQuick.Controls 2.2 import "qrc:////qml//styles-uit" as HifiStylesUit import "qrc:////qml//controls-uit" as HifiControlsUit import "qrc:////qml//controls" as HifiControls import "qrc:////qml//hifi" as Hifi Rectangle { HifiStylesUit.HifiConstants { id: hifi; } id: root; // Style color: "#404040"; property bool uiReady: false; property bool isRecordingSelectedGestures: false; property int listeningForGestureIndex: -1; Rectangle { z: 999; id: loading; anchors.fill: parent; visible: !root.uiReady; color: Qt.rgba(0.0, 0.0, 0.0, 0.85); // This object is always used in a popup. // This MouseArea is used to prevent a user from being // able to click on a button/mouseArea underneath the popup/section. MouseArea { anchors.fill: parent; hoverEnabled: true; propagateComposedEvents: false; } AnimatedImage { id: processingImage; source: "processing.gif" width: 74; height: width; anchors.verticalCenter: parent.verticalCenter; anchors.horizontalCenter: parent.horizontalCenter; } } Rectangle { z: 998; id: importPopup; anchors.fill: parent; visible: false; color: Qt.rgba(0.0, 0.0, 0.0, 0.85); // This object is always used in a popup. // This MouseArea is used to prevent a user from being // able to click on a button/mouseArea underneath the popup/section. MouseArea { anchors.fill: parent; hoverEnabled: true; propagateComposedEvents: false; } Rectangle { anchors.centerIn: parent; width: parent.width - 30; height: parent.height - 30; color: hifi.colors.baseGrayHighlight; HifiStylesUit.RalewaySemiBold { id: importPopupTitleText; text: "Import Gesture Data"; // Anchors anchors.top: parent.top; anchors.left: parent.left; anchors.leftMargin: 16; anchors.right: parent.right; height: 40; size: 22; // Style color: hifi.colors.white; // Alignment horizontalAlignment: Text.AlignLeft; verticalAlignment: Text.AlignVCenter; } TextArea { id: dataTextArea; font.family: "Fira Sans Regular"; placeholderText: "Paste Gesture Data Here"; font.pixelSize: 12; // Anchors anchors.top: importPopupTitleText.bottom; anchors.left: parent.left; anchors.right: parent.right; anchors.bottom: importButton.top; anchors.bottomMargin: 8; // Style background: Rectangle { anchors.fill: parent; color: dataTextArea.activeFocus ? hifi.colors.black : hifi.colors.baseGrayShadow border.width: dataTextArea.activeFocus ? 1 : 0; border.color: dataTextArea.activeFocus ? hifi.colors.primaryHighlight : hifi.colors.textFieldLightBackground; } color: hifi.colors.white; textFormat: TextEdit.PlainText; wrapMode: TextEdit.Wrap; activeFocusOnPress: true; activeFocusOnTab: true; } HifiControlsUit.Button { text: "CANCEL"; colorScheme: hifi.colorSchemes.dark; color: hifi.buttons.black; anchors.right: importButton.left; anchors.rightMargin: 8; anchors.bottom: parent.bottom; anchors.bottomMargin: 8; width: 100; height: 50; onClicked: { importPopup.visible = false; } } HifiControlsUit.Button { id: importButton; text: "IMPORT"; colorScheme: hifi.colorSchemes.dark; color: hifi.buttons.blue; anchors.right: parent.right; anchors.rightMargin: 8; anchors.bottom: parent.bottom; anchors.bottomMargin: 8; width: 100; height: 50; onClicked: { sendToScript({method: 'importGestureData', data: dataTextArea.text}); dataTextArea.text = ""; importPopup.visible = false; } } } } // // TITLE BAR START // Rectangle { id: titleBarContainer; // Size width: root.width; height: 60; // Anchors anchors.left: parent.left; anchors.top: parent.top; color: "#121212"; // Title bar text HifiStylesUit.RalewaySemiBold { id: titleBarText; text: "Gestures"; // Anchors anchors.left: parent.left; anchors.leftMargin: 16; width: paintedWidth; height: parent.height; size: 22; // Style color: hifi.colors.white; // Alignment horizontalAlignment: Text.AlignLeft; verticalAlignment: Text.AlignVCenter; } Rectangle { id: gestureDetectionStatus; property bool detected: false; color: root.listeningForGestureIndex === -1 ? hifi.colors.black : (gestureDetectionStatus.detected ? hifi.colors.greenHighlight : hifi.colors.redAccent); anchors.right: parent.right; anchors.top: parent.top; anchors.bottom: parent.bottom; width: 280; HifiStylesUit.RalewaySemiBold { text: root.listeningForGestureIndex === -1 ? "Not Listening..." : (gestureDetectionStatus.detected ? "DETECTED!" : "Listening..."); // Anchors anchors.fill: parent; size: 22; // Style color: hifi.colors.white; // Alignment horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter; } Timer { id: gestureDetectionStatusTimer; interval: 1000; running: false; repeat: false; onTriggered: { gestureDetectionStatus.detected = false; } } } } // // TITLE BAR END // Item { id: masterSwitchContainer; anchors.top: titleBarContainer.bottom; anchors.topMargin: 16; anchors.left: parent.left; anchors.leftMargin: 16; anchors.right: parent.right; anchors.rightMargin: 16; height: childrenRect.height; HifiStylesUit.RalewaySemiBold { id: masterSwitchLabel; text: "Enable Gesture Recording Mapping"; // Anchors anchors.left: parent.left; anchors.top: parent.top; width: paintedWidth; height: paintedHeight; size: 22; // Style color: hifi.colors.white; // Alignment horizontalAlignment: Text.AlignLeft; verticalAlignment: Text.AlignVCenter; } Switch { id: masterSwitch; enabled: !root.isRecordingSelectedGestures; focusPolicy: Qt.ClickFocus; anchors.left: parent.left; anchors.top: masterSwitchLabel.bottom; anchors.topMargin: 8; width: 70; height: 30; hoverEnabled: true; onHoveredChanged: { if (hovered) { switchHandle.color = hifi.colors.blueHighlight; } else { switchHandle.color = hifi.colors.lightGray; } } onClicked: { sendToScript({method: 'enableRecordingSwitchChanged', status: checked}); } background: Rectangle { color: parent.checked ? "#1FC6A6" : hifi.colors.white; implicitWidth: parent.width; implicitHeight: parent.height; radius: height/2; } indicator: Rectangle { id: switchHandle; implicitWidth: parent.height - 4; implicitHeight: implicitWidth; radius: implicitWidth/2; border.color: "#E3E3E3"; color: "#404040"; x: Math.max(4, Math.min(parent.width - width - 4, parent.visualPosition * parent.width - (width / 2) - 4)) y: parent.height / 2 - height / 2; Behavior on x { enabled: !parent.parent.down SmoothedAnimation { velocity: 200 } } } } HifiStylesUit.RalewayRegular { text: "Click right stick to start and stop recording"; // Anchors anchors.left: parent.left; anchors.top: masterSwitch.bottom; anchors.topMargin: 8; width: paintedWidth; height: paintedHeight; size: 16; // Style color: hifi.colors.lightGrayText; // Alignment horizontalAlignment: Text.AlignLeft; verticalAlignment: Text.AlignVCenter; } } Item { id: jointSelectionContainer; anchors.top: masterSwitchContainer.bottom; anchors.topMargin: 24; anchors.left: parent.left; anchors.leftMargin: 16; anchors.right: parent.right; anchors.rightMargin: 16; height: childrenRect.height; property var selections: [leftHandCheckBox.checked, rightHandCheckBox.checked, headCheckBox.checked]; HifiStylesUit.RalewaySemiBold { id: jointSelectionLabel; text: "Record These:"; // Anchors anchors.left: parent.left; anchors.top: parent.top; anchors.right: parent.right; height: paintedHeight; size: 22; // Style color: hifi.colors.white; // Alignment horizontalAlignment: Text.AlignLeft; verticalAlignment: Text.AlignVCenter; } // Left hand HifiControlsUit.CheckBox { id: leftHandCheckBox; enabled: !root.isRecordingSelectedGestures; color: hifi.colors.white; colorScheme: hifi.colorSchemes.dark; anchors.left: parent.left; anchors.top: jointSelectionLabel.bottom; anchors.topMargin: 4; text: "Left Hand"; labelFontSize: 20; labelFontWeight: Font.Normal; boxSize: 24; onClicked: { sendToScript({method: 'updateJointSelections', selections: parent.selections}); } } // Right hand HifiControlsUit.CheckBox { id: rightHandCheckBox; enabled: !root.isRecordingSelectedGestures; color: hifi.colors.white; colorScheme: hifi.colorSchemes.dark; anchors.left: leftHandCheckBox.right; anchors.leftMargin: 16; anchors.top: jointSelectionLabel.bottom; anchors.topMargin: 4; text: "Right Hand"; labelFontSize: 20; labelFontWeight: Font.Normal; boxSize: 24; onClicked: { sendToScript({method: 'updateJointSelections', selections: parent.selections}); } } // Head hand HifiControlsUit.CheckBox { id: headCheckBox; enabled: !root.isRecordingSelectedGestures; color: hifi.colors.white; colorScheme: hifi.colorSchemes.dark; anchors.left: rightHandCheckBox.right; anchors.leftMargin: 16; anchors.top: jointSelectionLabel.bottom; anchors.topMargin: 4; text: "Head"; labelFontSize: 20; labelFontWeight: Font.Normal; boxSize: 24; onClicked: { sendToScript({method: 'updateJointSelections', selections: parent.selections}); } } } Rectangle { id: savedGesturesContainer; anchors.top: jointSelectionContainer.bottom; anchors.topMargin: 8; anchors.left: parent.left; anchors.right: parent.right; anchors.bottom: maxIgnoreDistanceContainer.top; anchors.bottomMargin: 8; color: "#0c0c0c"; Item { id: savedGesturesListTitleBar; anchors.top: parent.top; anchors.topMargin: 8; anchors.left: parent.left; anchors.leftMargin: 16; anchors.right: parent.right; height: 40; HifiStylesUit.RalewaySemiBold { id: savedGesturesListTitleText; text: "Recorded Gestures"; // Anchors anchors.left: parent.left; anchors.verticalCenter: parent.verticalCenter; width: paintedWidth; height: paintedHeight; size: 22; // Style color: hifi.colors.white; // Alignment horizontalAlignment: Text.AlignLeft; verticalAlignment: Text.AlignVCenter; } HifiControlsUit.Button { id: importButtonMain; text: "IMPORT"; colorScheme: hifi.colorSchemes.dark; color: hifi.buttons.blue; anchors.left: savedGesturesListTitleText.right; anchors.leftMargin: 10; anchors.verticalCenter: parent.verticalCenter; width: 75; height: 30; onClicked: { importPopup.visible = true; } } HifiControlsUit.Button { id: clearButton; text: "CLEAR"; enabled: savedGesturesModel.count !== 0; colorScheme: hifi.colorSchemes.dark; color: hifi.buttons.red; anchors.verticalCenter: parent.verticalCenter; anchors.left: importButtonMain.right; anchors.leftMargin: 10; width: 60; height: 30; onClicked: { root.listeningForGestureIndex = -1; sendToScript({method: 'clearRecordedGestures'}); savedGesturesModel.clear(); } } } Rectangle { visible: savedGesturesModel.count === 0; anchors.top: savedGesturesListTitleBar.bottom; anchors.topMargin: 8; anchors.left: parent.left; anchors.right: parent.right; anchors.bottom: parent.bottom; color: hifi.colors.black; HifiStylesUit.RalewaySemiBold { text: "No Gestures Recorded (Yet!)"; anchors.fill: parent; // Anchors size: 22; // Style color: hifi.colors.white; // Alignment horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter; } } Item { id: sensitivity; visible: savedGesturesList.visible; anchors.top: savedGesturesListTitleBar.bottom; anchors.topMargin: 8; anchors.left: parent.left; anchors.leftMargin: 16; anchors.right: parent.right; height: 35; HifiStylesUit.RalewaySemiBold { id: sensitivityLabel; text: "Detection Sensitivity (" + sensitivitySlider.value.toFixed(1) + "x): "; size: 16; color: hifi.colors.white; anchors.left: parent.left; anchors.top: parent.top; anchors.bottom: parent.bottom; width: 205; horizontalAlignment: Text.AlignLeft; verticalAlignment: Text.AlignVCenter; } HifiControlsUit.Slider { id: sensitivitySlider; anchors.top: parent.top; anchors.bottom: parent.bottom; anchors.right: resetSensitivity.left; anchors.rightMargin: 8; anchors.left: sensitivityLabel.right; anchors.leftMargin: 8; colorScheme: hifi.colorSchemes.dark; from: 0.1; to: 5.0; value: 1.0; stepSize: 0.1; onValueChanged: { sendToScript({method: 'updateSensitivity', sensitivity: value}); } onPressedChanged: { if (!pressed) { sendToScript({method: 'updateSensitivity', sensitivity: value}); } } } HifiControlsUit.GlyphButton { id: resetSensitivity; anchors.verticalCenter: parent.verticalCenter; anchors.right: parent.right; anchors.rightMargin: 16; height: parent.height - 8; width: height; glyph: hifi.glyphs.reload; onClicked: { sensitivitySlider.value = 1.0; } } } ListModel { id: savedGesturesModel; } ListView { id: savedGesturesList; visible: savedGesturesModel.count !== 0; clip: true; model: savedGesturesModel; snapMode: ListView.SnapToItem; anchors.top: sensitivity.bottom; anchors.topMargin: 12; anchors.left: parent.left; anchors.right: parent.right; anchors.bottom: parent.bottom; delegate: Item { width: parent.width; height: 40; HifiStylesUit.FiraSansRegular { id: recordedTime; text: model.recordedTime; // Text size size: 20; // Anchors anchors.left: parent.left; anchors.leftMargin: 12; height: parent.height; width: 80; elide: Text.ElideRight; // Style color: hifi.colors.white; // Alignment verticalAlignment: Text.AlignVCenter; } HifiControlsUit.CheckBox { id: lBox; enabled: false; checked: model.recordedLeftHand; color: hifi.colors.white; colorScheme: hifi.colorSchemes.dark; anchors.left: recordedTime.right; anchors.leftMargin: 12; width: 55; height: parent.height; text: "L"; labelFontSize: 18; labelFontWeight: Font.Normal; boxSize: 16; } HifiControlsUit.CheckBox { id: rBox; enabled: false; checked: model.recordedRightHand; color: hifi.colors.white; colorScheme: hifi.colorSchemes.dark; anchors.left: lBox.right; anchors.leftMargin: 4; width: 55; height: parent.height; text: "R"; labelFontSize: 18; labelFontWeight: Font.Normal; boxSize: 16; } HifiControlsUit.CheckBox { id: hmdBox; enabled: false; checked: model.recordedHead; color: hifi.colors.white; colorScheme: hifi.colorSchemes.dark; anchors.left: rBox.right; anchors.leftMargin: 4; width: 80; height: parent.height; text: "HMD"; labelFontSize: 18; labelFontWeight: Font.Normal; boxSize: 16; } HifiControlsUit.Button { id: makeActiveButton; enabled: !root.isRecordingSelectedGestures; text: root.listeningForGestureIndex === model.index ? "LISTENING" : "LISTEN"; colorScheme: hifi.colorSchemes.dark; color: root.listeningForGestureIndex === model.index ? hifi.buttons.blue : hifi.buttons.black; anchors.right: exportButton.left; anchors.rightMargin: 12; anchors.verticalCenter: parent.verticalCenter; height: parent.height - 10; onClicked: { if (root.listeningForGestureIndex === model.index) { root.listeningForGestureIndex = -1; } else { root.listeningForGestureIndex = model.index; } sendToScript({method: 'modifyListeningForGestureIndex', listeningForGestureIndex: root.listeningForGestureIndex}); } } HifiControlsUit.GlyphButton { id: exportButton; color: hifi.buttons.blue; enabled: !root.isRecordingSelectedGestures; glyph: hifi.glyphs.install; width: parent.height - 10; size: height; anchors.right: parent.right; anchors.rightMargin: 8; anchors.verticalCenter: parent.verticalCenter; onClicked: { sendToScript({method: 'copyDataToClipboard', index: model.index}); } } } } } Item { id: maxIgnoreDistanceContainer; anchors.bottom: manualRecordButton.top; anchors.bottomMargin: 8; anchors.left: parent.left; anchors.leftMargin: 16; anchors.right: parent.right; anchors.rightMargin: 16; height: 40; HifiStylesUit.RalewaySemiBold { id: maxIgnoreDistanceLabel; text: "Max Ignore Distance (m):"; // Anchors anchors.left: parent.left; anchors.top: parent.top; anchors.bottom: parent.bottom; width: paintedWidth; size: 22; // Style color: hifi.colors.white; // Alignment horizontalAlignment: Text.AlignLeft; verticalAlignment: Text.AlignVCenter; } HifiControlsUit.TextField { id: maxIgnoreDistance; colorScheme: hifi.colorSchemes.dark; inputMethodHints: Qt.ImhDigitsOnly; // Anchors anchors.verticalCenter: parent.verticalCenter; anchors.left: maxIgnoreDistanceLabel.right; anchors.leftMargin: 8; width: 50; height: parent.height - 6; activeFocusOnPress: true; activeFocusOnTab: true; validator: IntValidator { bottom: 1; } onTextChanged: { sendToScript({method: 'updateMaxIgnoreDistance', distance: parseInt(text)}); } } } HifiControlsUit.Button { id: manualRecordButton; enabled: leftHandCheckBox.checked || rightHandCheckBox.checked || headCheckBox.checked; text: root.isRecordingSelectedGestures ? "STOP" : "START" + " RECORDING"; colorScheme: hifi.colorSchemes.dark; color: hifi.buttons.red; anchors.bottom: parent.bottom; anchors.bottomMargin: 16; anchors.left: parent.left; anchors.leftMargin: 16; anchors.right: parent.right; anchors.rightMargin: 16; height: 50; onClicked: { sendToScript({method: 'toggleManualDataCapture'}); } } // // FUNCTION DEFINITIONS START // // // Function Name: fromScript() // // Relevant Variables: // None // // Arguments: // message: The message sent from the Gestures JavaScript. // Messages are in format "{method, params}", like json-rpc. // // Description: // Called when a message is received from spectatorCamera.js. // function fromScript(message) { switch (message.method) { case 'initializeUI': masterSwitch.checked = message.masterSwitchOn; leftHandCheckBox.checked = message.jointDataToRecord[0]; rightHandCheckBox.checked = message.jointDataToRecord[1]; headCheckBox.checked = message.jointDataToRecord[2]; root.listeningForGestureIndex = message.currentlyDetectingGesture; sensitivitySlider.value = message.gestureDetectionSensitivityMultiplier; savedGesturesModel.clear(); maxIgnoreDistance.text = message.maxIgnoreDistance; root.uiReady = true; break; case 'updateIsRecordingSelectedGestures': root.isRecordingSelectedGestures = message.isRecordingSelectedGestures; break; case 'appendRecordedGesture': var gestureData = { index: message.index, recordedTime: message.recordedTime, recordedLeftHand: message.recordedJoints[0], recordedRightHand: message.recordedJoints[1], recordedHead: message.recordedJoints[2], }; savedGesturesModel.append(gestureData); break; case 'gestureDetected': gestureDetectionStatus.detected = true; gestureDetectionStatusTimer.restart(); break; default: console.log('Unrecognized message from gestures.js:', JSON.stringify(message)); } } signal sendToScript(var message); // // FUNCTION DEFINITIONS END // }