Merge pull request #13782 from ElderOrb/FB17184

Add Support for Managing Avatar Entities to the Avatar App
This commit is contained in:
John Conklin II 2018-08-22 10:20:04 -07:00 committed by GitHub
commit 32562511e8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 442 additions and 1140 deletions

View file

@ -103,7 +103,7 @@ FocusScope {
verticalCenter: parent.verticalCenter
}
size: hifi.fontSizes.textFieldInput
text: comboBox.currentText
text: comboBox.displayText ? comboBox.displayText : comboBox.currentText
elide: Text.ElideRight
color: comboBox.hovered || comboBox.popup.visible ? hifi.colors.baseGray : (isLightColorScheme ? hifi.colors.lightGray : hifi.colors.lightGrayText )
}

View file

@ -21,6 +21,7 @@ SpinBox {
id: hifi
}
inputMethodHints: Qt.ImhFormattedNumbersOnly
property int colorScheme: hifi.colorSchemes.light
readonly property bool isLightColorScheme: colorScheme === hifi.colorSchemes.light
property string label: ""
@ -91,7 +92,6 @@ SpinBox {
}
valueFromText: function(text, locale) {
spinBox.value = 0; // Force valueChanged signal to be emitted so that validator fires.
return Number.fromLocaleString(locale, text)*factor;
}
@ -104,6 +104,8 @@ SpinBox {
selectedTextColor: hifi.colors.black
selectionColor: hifi.colors.primaryHighlight
text: spinBox.textFromValue(spinBox.value, spinBox.locale) + suffix
inputMethodHints: spinBox.inputMethodHints
validator: spinBox.validator
verticalAlignment: Qt.AlignVCenter
leftPadding: spinBoxLabelInside.visible ? 30 : hifi.dimensions.textPadding
//rightPadding: hifi.dimensions.spinnerSize

View file

@ -162,9 +162,10 @@ Rectangle {
if (newItemIndex <= (allAvatars.count - 1)) {
pageOfAvatars.append(allAvatars.get(newItemIndex));
} else {
if(!pageOfAvatars.hasGetAvatars())
if (!pageOfAvatars.hasGetAvatars()) {
pageOfAvatars.appendGetAvatars();
}
}
pageOfAvatars.isUpdating = false;
} else if (message.method === getAvatarsMethod) {
@ -285,6 +286,9 @@ Rectangle {
onWearableSelected: {
emitSendToScript({'method' : 'selectWearable', 'entityID' : id});
}
onAddWearable: {
emitSendToScript({'method' : 'addWearable', 'avatarName' : avatarName, 'url' : url});
}
z: 3
}
@ -491,33 +495,10 @@ Rectangle {
anchors.verticalCenter: wearablesLabel.verticalCenter
glyphText: "\ue02e"
visible: avatarWearablesCount !== 0
onClicked: {
adjustWearables.open(currentAvatar);
}
}
// TextStyle3
RalewayRegular {
size: 22;
anchors.right: parent.right
anchors.verticalCenter: wearablesLabel.verticalCenter
font.underline: true
text: "Add"
color: 'black'
visible: avatarWearablesCount === 0
MouseArea {
anchors.fill: parent
onClicked: {
popup.showGetWearables(function() {
emitSendToScript({'method' : 'navigate', 'url' : 'hifi://AvatarIsland/11.5848,-8.10862,-2.80195'})
}, function(link) {
emitSendToScript({'method' : 'navigate', 'url' : link})
});
}
}
}
}
Rectangle {
@ -617,8 +598,9 @@ Rectangle {
pageOfAvatars.append(avatarItem);
}
if(pageOfAvatars.count !== itemsPerPage)
if (pageOfAvatars.count !== itemsPerPage) {
pageOfAvatars.appendGetAvatars();
}
currentPage = pageIndex;
pageOfAvatars.isUpdating = false;

View file

@ -1,5 +1,6 @@
import Hifi 1.0 as Hifi
import QtQuick 2.5
import QtQuick.Layouts 1.3
import "../../styles-uit"
import "../../controls-uit" as HifiControlsUit
import "../../controls" as HifiControls
@ -17,6 +18,7 @@ Rectangle {
signal adjustWearablesOpened(var avatarName);
signal adjustWearablesClosed(bool status, var avatarName);
signal addWearable(string avatarName, string url);
property bool modified: false;
Component.onCompleted: {
@ -30,6 +32,7 @@ Rectangle {
function open(avatar) {
adjustWearablesOpened(avatar.name);
modified = false;
visible = true;
avatarName = avatar.name;
wearablesModel = avatar.wearables;
@ -38,19 +41,23 @@ Rectangle {
function refresh(avatar) {
wearablesCombobox.model.clear();
wearablesCombobox.currentIndex = -1;
for (var i = 0; i < avatar.wearables.count; ++i) {
var wearable = avatar.wearables.get(i).properties;
for (var j = (wearable.modelURL.length - 1); j >= 0; --j) {
if (wearable.modelURL[j] === '/') {
wearable.text = wearable.modelURL.substring(j + 1) + ' [%jointIndex%]'.replace('%jointIndex%', jointNames[wearable.parentJointIndex]);
wearable.text = wearable.modelURL.substring(j + 1);
break;
}
}
wearablesCombobox.model.append(wearable);
}
if (wearablesCombobox.model.count !== 0) {
wearablesCombobox.currentIndex = 0;
}
}
function refreshWearable(wearableID, wearableIndex, properties, updateUI) {
if (wearableIndex === -1) {
@ -71,9 +78,9 @@ Rectangle {
if (updateUI) {
if (prop === 'localPosition') {
position.set(wearable[prop]);
positionVector.set(wearable[prop]);
} else if (prop === 'localRotationAngles') {
rotation.set(wearable[prop]);
rotationVector.set(wearable[prop]);
} else if (prop === 'dimensions') {
scalespinner.set(wearable[prop].x / wearable.naturalDimensions.x);
}
@ -84,7 +91,7 @@ Rectangle {
}
function getCurrentWearable() {
return wearablesCombobox.model.get(wearablesCombobox.currentIndex)
return wearablesCombobox.currentIndex !== -1 ? wearablesCombobox.model.get(wearablesCombobox.currentIndex) : null;
}
function selectWearableByID(entityID) {
@ -115,16 +122,99 @@ Rectangle {
Column {
anchors.top: parent.top
anchors.topMargin: 15
anchors.topMargin: 12
anchors.horizontalCenter: parent.horizontalCenter
spacing: 20
width: parent.width - 30 * 2
width: parent.width - 22 * 2
Column {
width: parent.width
Rectangle {
color: hifi.colors.orangeHighlight
anchors.left: parent.left
anchors.right: parent.right
height: 70
visible: HMD.active
RalewayRegular {
anchors.fill: parent
anchors.leftMargin: 18
anchors.rightMargin: 18
size: 20;
lineHeightMode: Text.FixedHeight
lineHeight: 23;
text: "Tip: You can use hand controllers to grab and adjust your wearables"
wrapMode: Text.WordWrap
}
}
Rectangle {
anchors.left: parent.left
anchors.right: parent.right
height: 12 // spacing
visible: HMD.active
}
RowLayout {
anchors.left: parent.left
anchors.right: parent.right
RalewayBold {
size: 15;
lineHeightMode: Text.FixedHeight
lineHeight: 18;
text: "Wearable"
anchors.verticalCenter: parent.verticalCenter
}
spacing: 10
RalewayBold {
size: 15;
lineHeightMode: Text.FixedHeight
lineHeight: 18;
text: "<a href='#'>Get more</a>"
linkColor: hifi.colors.blueHighlight
anchors.verticalCenter: parent.verticalCenter
onLinkActivated: {
popup.showGetWearables(function() {
emitSendToScript({'method' : 'navigate', 'url' : 'hifi://AvatarIsland/11.5848,-8.10862,-2.80195'})
}, function(link) {
emitSendToScript({'method' : 'navigate', 'url' : link})
});
}
}
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
RalewayBold {
size: 15;
lineHeightMode: Text.FixedHeight
lineHeight: 18;
text: "<a href='#'>Add custom</a>"
linkColor: hifi.colors.blueHighlight
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
onLinkActivated: {
popup.showSpecifyWearableUrl(function(url) {
console.debug('popup.showSpecifyWearableUrl: ', url);
addWearable(root.avatarName, url);
modified = true;
});
}
}
}
}
HifiControlsUit.ComboBox {
id: wearablesCombobox
anchors.left: parent.left
anchors.right: parent.right
enabled: getCurrentWearable() !== null
comboBox.textRole: "text"
model: ListModel {
@ -143,44 +233,105 @@ Rectangle {
comboBox.onCurrentIndexChanged: {
var currentWearable = getCurrentWearable();
var position = currentWearable ? currentWearable.localPosition : { x : 0, y : 0, z : 0 };
var rotation = currentWearable ? currentWearable.localRotationAngles : { x : 0, y : 0, z : 0 };
var scale = currentWearable ? currentWearable.dimensions.x / currentWearable.naturalDimensions.x : 1.0;
var joint = currentWearable ? currentWearable.parentJointIndex : -1;
var soft = currentWearable ? currentWearable.relayParentJoints : false;
positionVector.set(position);
rotationVector.set(rotation);
scalespinner.set(scale);
jointsCombobox.set(joint);
isSoft.set(soft);
if (currentWearable) {
position.set(currentWearable.localPosition);
rotation.set(currentWearable.localRotationAngles);
scalespinner.set(currentWearable.dimensions.x / currentWearable.naturalDimensions.x)
wearableSelected(currentWearable.id);
}
}
}
}
Column {
width: parent.width
RalewayBold {
size: 15;
lineHeightMode: Text.FixedHeight
lineHeight: 18;
text: "Joint"
}
HifiControlsUit.ComboBox {
id: jointsCombobox
anchors.left: parent.left
anchors.right: parent.right
enabled: getCurrentWearable() !== null && !isSoft.checked
comboBox.displayText: isSoft.checked ? 'Hips' : comboBox.currentText
model: jointNames
property bool notify: false
function set(jointIndex) {
notify = false;
currentIndex = jointIndex;
notify = true;
}
function notifyJointChanged() {
modified = true;
var properties = {
parentJointIndex: currentIndex,
localPosition: {
x: positionVector.xvalue,
y: positionVector.yvalue,
z: positionVector.zvalue
},
localRotationAngles: {
x: rotationVector.xvalue,
y: rotationVector.yvalue,
z: rotationVector.zvalue,
}
};
wearableUpdated(getCurrentWearable().id, wearablesCombobox.currentIndex, properties);
}
onCurrentIndexChanged: {
if (notify) notifyJointChanged();
}
}
}
Column {
width: parent.width
spacing: 5
Row {
spacing: 20
// TextStyle5
FiraSansSemiBold {
RalewayBold {
id: positionLabel
size: 22;
size: 15;
lineHeightMode: Text.FixedHeight
lineHeight: 18;
text: "Position"
}
// TextStyle7
FiraSansRegular {
size: 18;
RalewayBold {
size: 15;
lineHeightMode: Text.FixedHeight
lineHeight: 16.9;
lineHeight: 18;
text: "m"
anchors.verticalCenter: positionLabel.verticalCenter
}
}
Vector3 {
id: position
id: positionVector
backgroundColor: "lightgray"
enabled: getCurrentWearable() !== null
function set(localPosition) {
notify = false;
@ -214,31 +365,33 @@ Rectangle {
Column {
width: parent.width
spacing: 5
Row {
spacing: 20
// TextStyle5
FiraSansSemiBold {
RalewayBold {
id: rotationLabel
size: 22;
size: 15;
lineHeightMode: Text.FixedHeight
lineHeight: 18;
text: "Rotation"
}
// TextStyle7
FiraSansRegular {
size: 18;
RalewayBold {
size: 15;
lineHeightMode: Text.FixedHeight
lineHeight: 16.9;
lineHeight: 18;
text: "deg"
anchors.verticalCenter: rotationLabel.verticalCenter
}
}
Vector3 {
id: rotation
id: rotationVector
backgroundColor: "lightgray"
enabled: getCurrentWearable() !== null
function set(localRotationAngles) {
notify = false;
@ -270,33 +423,66 @@ Rectangle {
}
}
Column {
width: parent.width
spacing: 5
// TextStyle5
FiraSansSemiBold {
size: 22;
text: "Scale"
}
Item {
width: parent.width
height: childrenRect.height
HifiControlsUit.CheckBox {
id: isSoft
enabled: getCurrentWearable() !== null
text: "Is soft"
labelFontSize: 15
labelFontWeight: Font.Bold
color: Qt.black
y: scalespinner.y
function set(value) {
notify = false;
checked = value
notify = true;
}
function notifyIsSoftChanged() {
modified = true;
var properties = {
relayParentJoints: checked
};
wearableUpdated(getCurrentWearable().id, wearablesCombobox.currentIndex, properties);
}
property bool notify: false;
onCheckedChanged: if (notify) notifyIsSoftChanged();
}
Column {
id: scalesColumn
anchors.right: parent.right
// TextStyle5
RalewayBold {
id: scaleLabel
size: 15;
lineHeightMode: Text.FixedHeight
lineHeight: 18;
text: "Scale"
}
HifiControlsUit.SpinBox {
id: scalespinner
enabled: getCurrentWearable() !== null
decimals: 2
realStepSize: 0.1
realFrom: 0.1
realTo: 3.0
realValue: 1.0
backgroundColor: "lightgray"
width: position.spinboxWidth
width: positionVector.spinboxWidth
colorScheme: hifi.colorSchemes.light
property bool notify: false;
onValueChanged: if(notify) notifyScaleChanged();
onRealValueChanged: if (notify) notifyScaleChanged();
function set(value) {
notify = false;
@ -320,26 +506,34 @@ Rectangle {
wearableUpdated(currentWearable.id, wearablesCombobox.currentIndex, properties);
}
}
}
}
Column {
width: parent.width
HifiControlsUit.Button {
fontSize: 18
height: 40
width: scalespinner.width
anchors.right: parent.right
color: hifi.buttons.red;
colorScheme: hifi.colorSchemes.light;
text: "TAKE IT OFF"
onClicked: wearableDeleted(root.avatarName, getCurrentWearable().id);
onClicked: {
modified = true;
wearableDeleted(root.avatarName, getCurrentWearable().id);
}
enabled: wearablesCombobox.model.count !== 0
anchors.verticalCenter: scalespinner.verticalCenter
}
}
}
}
DialogButtons {
yesButton.enabled: modified
anchors.bottom: parent.bottom
anchors.bottomMargin: 30
anchors.bottomMargin: 57
anchors.left: parent.left
anchors.leftMargin: 30
anchors.right: parent.right

View file

@ -24,8 +24,9 @@ ListModel {
function makeThumbnailUrl(avatarUrl) {
var marketId = extractMarketId(avatarUrl);
if(marketId === '')
if (marketId === '') {
return '';
}
var avatarThumbnailUrl = "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/%marketId%/large/hifi-mp-%marketId%.jpg"
.split('%marketId%').join(marketId);
@ -42,7 +43,6 @@ ListModel {
'thumbnailUrl' : avatarThumbnailUrl,
'avatarUrl' : avatar.avatarUrl,
'wearables' : avatar.avatarEntites ? avatar.avatarEntites : [],
'attachments' : avatar.attachments ? avatar.attachments : [],
'entry' : avatar,
'getMoreAvatars' : false
};
@ -115,10 +115,12 @@ ListModel {
}
function compareNumericObjects(o1, o2) {
if(o1 === undefined && o2 !== undefined)
if (o1 === undefined && o2 !== undefined) {
return false;
if(o1 !== undefined && o2 === undefined)
}
if (o1 !== undefined && o2 === undefined) {
return false;
}
for (var prop in o1) {
if (o1.hasOwnProperty(prop) && o2.hasOwnProperty(prop)) {
@ -164,18 +166,12 @@ ListModel {
{'propertyName' : 'marketplaceID'},
{'propertyName' : 'itemName'},
{'propertyName' : 'script'},
{'propertyName' : 'relayParentJoints'},
{'propertyName' : 'localPosition', 'comparer' : compareNumericObjects},
{'propertyName' : 'localRotationAngles', 'comparer' : compareNumericObjects},
{'propertyName' : 'dimensions', 'comparer' : compareNumericObjects}], 'properties')
}
function compareAttachments(a1, a2) {
return compareObjects(a1, a2, [{'propertyName' : 'position', 'comparer' : compareNumericObjects},
{'propertyName' : 'orientation'},
{'propertyName' : 'parentJointIndex'},
{'propertyName' : 'modelurl'}])
}
function findAvatarIndexByValue(avatar) {
var index = -1;
@ -185,13 +181,11 @@ ListModel {
var thesame = true;
var bookmarkedAvatar = allAvatars.get(i);
if(bookmarkedAvatar.avatarUrl !== avatar.avatarUrl)
if (bookmarkedAvatar.avatarUrl !== avatar.avatarUrl) {
continue;
}
if(bookmarkedAvatar.avatarScale !== avatar.avatarScale)
continue;
if(!modelsAreEqual(bookmarkedAvatar.attachments, avatar.attachments, compareAttachments)) {
if (bookmarkedAvatar.avatarScale !== avatar.avatarScale) {
continue;
}
@ -219,8 +213,9 @@ ListModel {
function findAvatar(avatarName) {
var avatarIndex = findAvatarIndex(avatarName);
if(avatarIndex === -1)
if (avatarIndex === -1) {
return undefined;
}
return get(avatarIndex);
}

View file

@ -18,16 +18,39 @@ MessageBox {
popup.button2text = 'CONFIRM';
popup.onButton2Clicked = function() {
if(callback)
if (callback) {
callback();
}
popup.close();
}
popup.onLinkClicked = function(link) {
if(linkCallback)
if (linkCallback) {
linkCallback(link);
}
}
popup.open();
popup.inputText.forceActiveFocus();
}
function showSpecifyWearableUrl(callback) {
popup.button2text = 'CONFIRM'
popup.button1text = 'CANCEL'
popup.titleText = 'Specify Wearable URL'
popup.bodyText = 'If you want to add a custom wearable, you can specify the URL of the wearable file here.'
popup.inputText.visible = true;
popup.inputText.placeholderText = 'Enter Wearable URL';
popup.onButton2Clicked = function() {
if (callback) {
callback(popup.inputText.text);
}
popup.close();
}
popup.open();
popup.inputText.forceActiveFocus();
@ -41,23 +64,25 @@ MessageBox {
popup.button1text = 'CANCEL'
popup.titleText = 'Get Wearables'
popup.bodyText = 'Buy wearables from <b><a href="app://marketplace">Marketplace.</a></b>' + '<br/>' +
'Use wearables in <b><a href="app://purchases">My Purchases.</a></b>' + '<br/>' + '<br/>' +
'Visit “AvatarIsland” to get wearables.'
'Wear wearable from <b><a href="app://purchases">My Purchases.</a></b>' + '<br/>' + '<br/>' +
'Visit “AvatarIsland” to get wearables'
popup.imageSource = getWearablesUrl;
popup.onButton2Clicked = function() {
popup.close();
if(callback)
if (callback) {
callback();
}
}
popup.onLinkClicked = function(link) {
popup.close();
if(linkCallback)
if (linkCallback) {
linkCallback(link);
}
}
popup.open();
}
@ -72,9 +97,10 @@ MessageBox {
popup.onButton2Clicked = function() {
popup.close();
if(callback)
if (callback) {
callback();
}
}
popup.open();
}
@ -87,9 +113,10 @@ MessageBox {
popup.onButton2Clicked = function() {
popup.close();
if(callback)
if (callback) {
callback();
}
}
popup.open();
}
@ -109,16 +136,18 @@ MessageBox {
popup.onButton2Clicked = function() {
popup.close();
if(callback)
if (callback) {
callback();
}
}
popup.onLinkClicked = function(link) {
popup.close();
if(linkCallback)
if (linkCallback) {
linkCallback(link);
}
}
popup.open();
}

View file

@ -20,6 +20,7 @@ Row {
spacing: spinboxSpace
property bool enabled: false;
property alias xvalue: xspinner.realValue
property alias yvalue: yspinner.realValue
property alias zvalue: zspinner.realValue
@ -35,6 +36,7 @@ Row {
realFrom: root.realFrom
realTo: root.realTo
realStepSize: root.realStepSize
enabled: root.enabled
}
HifiControlsUit.SpinBox {
@ -48,6 +50,7 @@ Row {
realFrom: root.realFrom
realTo: root.realTo
realStepSize: root.realStepSize
enabled: root.enabled
}
HifiControlsUit.SpinBox {
@ -61,5 +64,6 @@ Row {
realFrom: root.realFrom
realTo: root.realTo
realStepSize: root.realStepSize
enabled: root.enabled
}
}

View file

@ -1,48 +0,0 @@
//
// AttachmentsDialog.qml
//
// Created by David Rowe on 9 Mar 2017.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.5
import Qt.labs.settings 1.0
import "../../styles-uit"
import "../../windows"
import "content"
ScrollingWindow {
id: root
title: "Attachments"
objectName: "AttachmentsDialog"
width: 600
height: 600
resizable: true
destroyOnHidden: true
minSize: Qt.vector2d(400, 500)
HifiConstants { id: hifi }
// This is for JS/QML communication, which is unused in the AttachmentsDialog,
// but not having this here results in spurious warnings about a
// missing signal
signal sendToScript(var message);
property var settings: Settings {
category: "AttachmentsDialog"
property alias x: root.x
property alias y: root.y
property alias width: root.width
property alias height: root.height
}
function closeDialog() {
root.destroy();
}
AttachmentsContent { }
}

View file

@ -1,130 +0,0 @@
import QtQuick 2.7
import QtQuick.Controls 1.5
import QtQuick.XmlListModel 2.0
import QtQuick.Controls.Styles 1.4
import "../../windows"
import "../../js/Utils.js" as Utils
import "../models"
ModalWindow {
id: root
resizable: true
width: 640
height: 480
property var result;
signal selected(var modelUrl);
signal canceled();
Rectangle {
anchors.fill: parent
color: "white"
Item {
anchors { fill: parent; margins: 8 }
TextField {
id: filterEdit
anchors { left: parent.left; right: parent.right; top: parent.top }
style: TextFieldStyle { renderType: Text.QtRendering }
placeholderText: "filter"
onTextChanged: tableView.model.filter = text
}
TableView {
id: tableView
anchors { left: parent.left; right: parent.right; top: filterEdit.bottom; topMargin: 8; bottom: buttonRow.top; bottomMargin: 8 }
model: S3Model{}
onCurrentRowChanged: {
if (currentRow == -1) {
root.result = null;
return;
}
result = model.baseUrl + "/" + model.get(tableView.currentRow).key;
}
itemDelegate: Component {
Item {
clip: true
Text {
x: 3
anchors.verticalCenter: parent.verticalCenter
color: tableView.activeFocus && styleData.row === tableView.currentRow ? "yellow" : styleData.textColor
elide: styleData.elideMode
text: getText()
function getText() {
switch(styleData.column) {
case 1:
return Utils.formatSize(styleData.value)
default:
return styleData.value;
}
}
}
}
}
TableViewColumn {
role: "name"
title: "Name"
width: 200
}
TableViewColumn {
role: "size"
title: "Size"
width: 100
}
TableViewColumn {
role: "modified"
title: "Last Modified"
width: 200
}
Rectangle {
anchors.fill: parent
visible: tableView.model.status !== XmlListModel.Ready
color: "#7fffffff"
BusyIndicator {
anchors.centerIn: parent
width: 48; height: 48
running: true
}
}
}
Row {
id: buttonRow
anchors { right: parent.right; bottom: parent.bottom }
Button { action: acceptAction }
Button { action: cancelAction }
}
Action {
id: acceptAction
text: qsTr("OK")
enabled: root.result ? true : false
shortcut: "Return"
onTriggered: {
root.selected(root.result);
root.destroy();
}
}
Action {
id: cancelAction
text: qsTr("Cancel")
shortcut: "Esc"
onTriggered: {
root.canceled();
root.destroy();
}
}
}
}
}

View file

@ -1,230 +0,0 @@
import QtQuick 2.5
import "."
import ".."
import "../../tablet"
import "../../../styles-uit"
import "../../../controls-uit" as HifiControls
import "../../../windows"
Item {
height: column.height + 2 * 8
property var attachment;
HifiConstants { id: hifi }
signal selectAttachment();
signal deleteAttachment(var attachment);
signal updateAttachment();
property bool completed: false;
function doSelectAttachment(control, focus) {
if (focus) {
selectAttachment();
// Refocus control after possibly changing focus to attachment.
if (control.setControlFocus !== undefined) {
control.setControlFocus();
} else {
control.focus = true;
}
}
}
Rectangle { color: hifi.colors.baseGray; anchors.fill: parent; radius: 4 }
Component.onCompleted: {
jointChooser.model = MyAvatar.jointNames;
completed = true;
}
Column {
y: 8
id: column
anchors { left: parent.left; right: parent.right; margins: 20 }
spacing: 8
Item {
height: modelChooserButton.height + urlLabel.height + 4
anchors { left: parent.left; right: parent.right;}
HifiControls.Label { id: urlLabel; color: hifi.colors.lightGrayText; text: "Model URL"; anchors.top: parent.top;}
HifiControls.TextField {
id: modelUrl;
height: jointChooser.height;
colorScheme: hifi.colorSchemes.dark
anchors { bottom: parent.bottom; left: parent.left; rightMargin: 8; right: modelChooserButton.left }
text: attachment ? attachment.modelUrl : ""
onTextChanged: {
if (completed && attachment && attachment.modelUrl !== text) {
attachment.modelUrl = text;
updateAttachment();
}
}
onFocusChanged: doSelectAttachment(this, focus);
}
HifiControls.Button {
id: modelChooserButton;
text: "Choose";
color: hifi.buttons.black
colorScheme: hifi.colorSchemes.dark
anchors { right: parent.right; verticalCenter: modelUrl.verticalCenter }
Component {
id: modelBrowserBuilder;
ModelBrowserDialog {}
}
Component {
id: tabletModelBrowserBuilder;
TabletModelBrowserDialog {}
}
onClicked: {
var browser;
if (typeof desktop !== "undefined") {
browser = modelBrowserBuilder.createObject(desktop);
browser.selected.connect(function(newModelUrl){
modelUrl.text = newModelUrl;
});
} else {
browser = tabletModelBrowserBuilder.createObject(tabletRoot);
browser.selected.connect(function(newModelUrl){
modelUrl.text = newModelUrl;
tabletRoot.openModal = null;
});
browser.canceled.connect(function() {
tabletRoot.openModal = null;
});
// Make dialog modal.
tabletRoot.openModal = browser;
}
}
}
}
Item {
z: 1000
height: jointChooser.height + jointLabel.height + 4
anchors { left: parent.left; right: parent.right; }
HifiControls.Label {
id: jointLabel;
text: "Joint";
color: hifi.colors.lightGrayText;
anchors.top: parent.top
}
HifiControls.ComboBox {
id: jointChooser;
dropdownHeight: (typeof desktop !== "undefined") ? 480 : 206
anchors { bottom: parent.bottom; left: parent.left; right: parent.right }
colorScheme: hifi.colorSchemes.dark
currentIndex: attachment ? model.indexOf(attachment.jointName) : -1
onCurrentIndexChanged: {
if (completed && attachment && currentIndex != -1 && attachment.jointName !== model[currentIndex]) {
attachment.jointName = model[currentIndex];
updateAttachment();
}
}
onFocusChanged: doSelectAttachment(this, focus);
}
}
Item {
height: translation.height + translationLabel.height + 4
anchors { left: parent.left; right: parent.right; }
HifiControls.Label { id: translationLabel; color: hifi.colors.lightGrayText; text: "Translation"; anchors.top: parent.top; }
Translation {
id: translation;
anchors { left: parent.left; right: parent.right; bottom: parent.bottom}
vector: attachment ? attachment.translation : {x: 0, y: 0, z: 0};
onValueChanged: {
if (completed && attachment) {
attachment.translation = vector;
updateAttachment();
}
}
onControlFocusChanged: doSelectAttachment(this, controlFocus);
}
}
Item {
height: rotation.height + rotationLabel.height + 4
anchors { left: parent.left; right: parent.right; }
HifiControls.Label { id: rotationLabel; color: hifi.colors.lightGrayText; text: "Rotation"; anchors.top: parent.top; }
Rotation {
id: rotation;
anchors { left: parent.left; right: parent.right; bottom: parent.bottom; }
vector: attachment ? attachment.rotation : {x: 0, y: 0, z: 0};
onValueChanged: {
if (completed && attachment) {
attachment.rotation = vector;
updateAttachment();
}
}
onControlFocusChanged: doSelectAttachment(this, controlFocus);
}
}
Item {
height: scaleItem.height
anchors { left: parent.left; right: parent.right; }
Item {
id: scaleItem
height: scaleSpinner.height + scaleLabel.height + 4
width: parent.width / 3 - 8
anchors { right: parent.right; }
HifiControls.Label { id: scaleLabel; color: hifi.colors.lightGrayText; text: "Scale"; anchors.top: parent.top; }
HifiControls.SpinBox {
id: scaleSpinner;
anchors { left: parent.left; right: parent.right; bottom: parent.bottom; }
decimals: 2;
minimumValue: 0.01
maximumValue: 10
realStepSize: 0.05;
realValue: attachment ? attachment.scale : 1.0
colorScheme: hifi.colorSchemes.dark
onRealValueChanged: {
if (completed && attachment && attachment.scale !== realValue) {
attachment.scale = realValue;
updateAttachment();
}
}
onFocusChanged: doSelectAttachment(this, focus);
}
}
Item {
id: isSoftItem
height: scaleSpinner.height
anchors {
left: parent.left
bottom: parent.bottom
}
HifiControls.CheckBox {
id: soft
text: "Is soft"
anchors {
left: parent.left
verticalCenter: parent.verticalCenter
}
checked: attachment ? attachment.soft : false
colorScheme: hifi.colorSchemes.dark
onCheckedChanged: {
if (completed && attachment && attachment.soft !== checked) {
attachment.soft = checked;
updateAttachment();
}
}
onFocusChanged: doSelectAttachment(this, focus);
}
}
}
HifiControls.Button {
color: hifi.buttons.black
colorScheme: hifi.colorSchemes.dark
anchors { left: parent.left; right: parent.right; }
text: "Delete"
onClicked: deleteAttachment(root.attachment);
}
}
}

View file

@ -1,9 +0,0 @@
import "."
Vector3 {
decimals: 1;
stepSize: 1;
maximumValue: 180
minimumValue: -180
}

View file

@ -1,9 +0,0 @@
import "."
Vector3 {
decimals: 3;
stepSize: 0.01;
maximumValue: 10
minimumValue: -10
}

View file

@ -1,112 +0,0 @@
import QtQuick 2.5
import "../../../styles-uit"
import "../../../controls-uit" as HifiControls
import "../../../windows"
Item {
id: root
implicitHeight: xspinner.height
readonly property real spacing: 8
property real spinboxWidth: (width / 3) - spacing
property var vector;
property real decimals: 0
property real stepSize: 1
property real maximumValue: 99
property real minimumValue: 0
property bool controlFocus: false; // True if one of the ordinate controls has focus.
property var controlFocusControl: undefined
signal valueChanged();
function setControlFocus() {
if (controlFocusControl) {
controlFocusControl.focus = true;
// The controlFocus value is updated via onFocusChanged.
}
}
function setFocus(control, focus) {
if (focus) {
controlFocusControl = control;
setControlFocusTrue.start(); // After any subsequent false from previous control.
} else {
controlFocus = false;
}
}
Timer {
id: setControlFocusTrue
interval: 50
repeat: false
running: false
onTriggered: {
controlFocus = true;
}
}
HifiConstants { id: hifi }
HifiControls.SpinBox {
id: xspinner
width: root.spinboxWidth
anchors { left: parent.left }
realValue: root.vector.x
labelInside: "X:"
colorScheme: hifi.colorSchemes.dark
colorLabelInside: hifi.colors.redHighlight
decimals: root.decimals
realStepSize: root.stepSize
maximumValue: root.maximumValue
minimumValue: root.minimumValue
onRealValueChanged: {
if (realValue !== vector.x) {
vector.x = realValue
root.valueChanged();
}
}
onFocusChanged: setFocus(this, focus);
}
HifiControls.SpinBox {
id: yspinner
width: root.spinboxWidth
anchors { horizontalCenter: parent.horizontalCenter }
realValue: root.vector.y
labelInside: "Y:"
colorLabelInside: hifi.colors.greenHighlight
colorScheme: hifi.colorSchemes.dark
decimals: root.decimals
realStepSize: root.stepSize
maximumValue: root.maximumValue
minimumValue: root.minimumValue
onRealValueChanged: {
if (realValue !== vector.y) {
vector.y = realValue
root.valueChanged();
}
}
onFocusChanged: setFocus(this, focus);
}
HifiControls.SpinBox {
id: zspinner
width: root.spinboxWidth
anchors { right: parent.right; }
realValue: root.vector.z
labelInside: "Z:"
colorLabelInside: hifi.colors.primaryHighlight
colorScheme: hifi.colorSchemes.dark
decimals: root.decimals
realStepSize: root.stepSize
maximumValue: root.maximumValue
minimumValue: root.minimumValue
onRealValueChanged: {
if (realValue !== vector.z) {
vector.z = realValue
root.valueChanged();
}
}
onFocusChanged: setFocus(this, focus);
}
}

View file

@ -1,282 +0,0 @@
import QtQuick 2.7
import QtQuick.Controls 2.3
import QtQuick.Dialogs 1.2 as OriginalDialogs
import "../../../styles-uit"
import "../../../controls-uit" as HifiControls
import "../../../windows"
import "../attachments"
Item {
id: content
readonly property var originalAttachments: MyAvatar.getAttachmentsVariant();
property var attachments: [];
function reload(){
content.attachments = [];
var currentAttachments = MyAvatar.getAttachmentsVariant();
listView.model.clear();
for (var i = 0; i < currentAttachments.length; ++i) {
var attachment = currentAttachments[i];
content.attachments.push(attachment);
listView.model.append({});
}
}
Connections {
id: onAttachmentsChangedConnection
target: MyAvatar
onAttachmentsChanged: reload()
}
Component.onCompleted: {
reload()
}
function setAttachmentsVariant(attachments) {
onAttachmentsChangedConnection.enabled = false;
MyAvatar.setAttachmentsVariant(attachments);
onAttachmentsChangedConnection.enabled = true;
}
Column {
width: pane.width
Rectangle {
width: parent.width
height: root.height - (keyboardEnabled && keyboardRaised ? 200 : 0)
color: hifi.colors.baseGray
Rectangle {
id: attachmentsBackground
anchors {
left: parent.left; right: parent.right; top: parent.top; bottom: newAttachmentButton.top;
margins: hifi.dimensions.contentMargin.x
bottomMargin: hifi.dimensions.contentSpacing.y
}
color: hifi.colors.baseGrayShadow
radius: 4
ListView {
id: listView
anchors {
top: parent.top
left: parent.left
right: scrollBar.left
bottom: parent.bottom
margins: 4
}
clip: true
cacheBuffer: 4000
model: ListModel {}
delegate: Item {
id: attachmentDelegate
implicitHeight: attachmentView.height + 8;
implicitWidth: attachmentView.width
MouseArea {
// User can click on whitespace to select item.
anchors.fill: parent
propagateComposedEvents: true
onClicked: {
listView.currentIndex = index;
attachmentsBackground.forceActiveFocus(); // Unfocus from any control.
mouse.accepted = false;
}
}
Attachment {
id: attachmentView
width: listView.width
attachment: content.attachments[index]
onSelectAttachment: {
listView.currentIndex = index;
}
onDeleteAttachment: {
attachments.splice(index, 1);
listView.model.remove(index, 1);
}
onUpdateAttachment: {
setAttachmentsVariant(attachments);
}
}
}
onCountChanged: {
setAttachmentsVariant(attachments);
}
/*
// DEBUG
highlight: Rectangle { color: "#40ffff00" }
highlightFollowsCurrentItem: true
*/
onHeightChanged: {
// Keyboard has been raised / lowered.
positionViewAtIndex(listView.currentIndex, ListView.SnapPosition);
}
onCurrentIndexChanged: {
if (!yScrollTimer.running) {
scrollSlider.y = currentIndex * (scrollBar.height - scrollSlider.height) / (listView.count - 1);
}
}
onContentYChanged: {
// User may have dragged content up/down.
yScrollTimer.restart();
}
Timer {
id: yScrollTimer
interval: 200
repeat: false
running: false
onTriggered: {
var index = (listView.count - 1) * listView.contentY / (listView.contentHeight - scrollBar.height);
index = Math.round(index);
listView.currentIndex = index;
scrollSlider.y = index * (scrollBar.height - scrollSlider.height) / (listView.count - 1);
}
}
}
Rectangle {
id: scrollBar
property bool scrolling: listView.contentHeight > listView.height
anchors {
top: parent.top
right: parent.right
bottom: parent.bottom
topMargin: 4
bottomMargin: 4
}
width: scrolling ? 18 : 0
radius: attachmentsBackground.radius
color: hifi.colors.baseGrayShadow
MouseArea {
anchors.fill: parent
onClicked: {
var index = listView.currentIndex;
index = index + (mouse.y <= scrollSlider.y ? -1 : 1);
if (index < 0) {
index = 0;
}
if (index > listView.count - 1) {
index = listView.count - 1;
}
listView.currentIndex = index;
}
}
Rectangle {
id: scrollSlider
anchors {
right: parent.right
rightMargin: 3
}
width: 16
height: (listView.height / listView.contentHeight) * listView.height
radius: width / 2
color: hifi.colors.lightGray
visible: scrollBar.scrolling;
onYChanged: {
var index = y * (listView.count - 1) / (scrollBar.height - scrollSlider.height);
index = Math.round(index);
listView.currentIndex = index;
}
MouseArea {
anchors.fill: parent
drag.target: scrollSlider
drag.axis: Drag.YAxis
drag.minimumY: 0
drag.maximumY: scrollBar.height - scrollSlider.height
}
}
}
}
HifiControls.Button {
id: newAttachmentButton
anchors {
left: parent.left
right: parent.right
bottom: buttonRow.top
margins: hifi.dimensions.contentMargin.x;
topMargin: hifi.dimensions.contentSpacing.y
bottomMargin: hifi.dimensions.contentSpacing.y
}
text: "New Attachment"
color: hifi.buttons.black
colorScheme: hifi.colorSchemes.dark
onClicked: {
var template = {
modelUrl: "",
translation: { x: 0, y: 0, z: 0 },
rotation: { x: 0, y: 0, z: 0 },
scale: 1,
jointName: MyAvatar.jointNames[0],
soft: false
};
attachments.push(template);
listView.model.append({});
setAttachmentsVariant(attachments);
}
}
Row {
id: buttonRow
spacing: 8
anchors {
right: parent.right
bottom: parent.bottom
margins: hifi.dimensions.contentMargin.x
topMargin: hifi.dimensions.contentSpacing.y
bottomMargin: hifi.dimensions.contentSpacing.y
}
HifiControls.Button {
action: okAction
color: hifi.buttons.black
colorScheme: hifi.colorSchemes.dark
}
HifiControls.Button {
action: cancelAction
color: hifi.buttons.black
colorScheme: hifi.colorSchemes.dark
}
}
Action {
id: cancelAction
text: "Cancel"
onTriggered: {
setAttachmentsVariant(originalAttachments);
closeDialog();
}
}
Action {
id: okAction
text: "OK"
onTriggered: {
for (var i = 0; i < attachments.length; ++i) {
console.log("Attachment " + i + ": " + attachments[i]);
}
setAttachmentsVariant(attachments);
closeDialog();
}
}
}
}
}

View file

@ -1,103 +0,0 @@
//
// TabletAttachmentsDialog.qml
//
// Created by David Rowe on 9 Mar 2017.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.5
import "../../controls-uit" as HifiControls
import "../../styles-uit"
import "../dialogs/content"
Item {
id: root
objectName: "AttachmentsDialog"
property string title: "Avatar Attachments"
property bool keyboardEnabled: false
property bool keyboardRaised: false
property bool punctuationMode: false
signal sendToScript(var message);
anchors.fill: parent
HifiConstants { id: hifi }
Rectangle {
id: pane // Surrogate for ScrollingWindow's pane.
anchors.fill: parent
}
function closeDialog() {
Tablet.getTablet("com.highfidelity.interface.tablet.system").gotoHomeScreen();
}
anchors.topMargin: hifi.dimensions.tabletMenuHeader // Space for header.
HifiControls.TabletHeader {
id: header
title: root.title
anchors {
left: parent.left
right: parent.right
bottom: parent.top
}
}
AttachmentsContent {
id: attachments
anchors {
top: header.bottom
left: parent.left
right: parent.right
bottom: keyboard.top
}
MouseArea {
// Defocuses any current control so that the keyboard gets hidden.
id: defocuser
anchors.fill: parent
propagateComposedEvents: true
acceptedButtons: Qt.AllButtons
onPressed: {
parent.forceActiveFocus();
mouse.accepted = false;
}
}
}
HifiControls.Keyboard {
id: keyboard
raised: parent.keyboardEnabled && parent.keyboardRaised
numeric: parent.punctuationMode
anchors {
left: parent.left
right: parent.right
bottom: parent.bottom
}
}
MouseArea {
id: activator
anchors.fill: parent
propagateComposedEvents: true
enabled: true
acceptedButtons: Qt.AllButtons
onPressed: {
mouse.accepted = false;
}
}
Component.onCompleted: {
keyboardEnabled = HMD.active;
}
}

View file

@ -25,6 +25,7 @@
#include <avatar/AvatarManager.h>
#include <EntityItemID.h>
#include <EntityTree.h>
#include <ModelEntityItem.h>
#include <PhysicalEntitySimulation.h>
#include <EntityEditPacketSender.h>
#include <VariantMapToScriptValue.h>
@ -35,7 +36,7 @@
#include "QVariantGLM.h"
#include <QtQuick/QQuickWindow>
#include <memory>
void addAvatarEntities(const QVariantList& avatarEntities) {
auto nodeList = DependencyManager::get<NodeList>();
@ -145,8 +146,8 @@ void AvatarBookmarks::removeBookmark(const QString& bookmarkName) {
}
bool isWearableEntity(const EntityItemPointer& entity) {
return entity->isVisible() && entity->getParentJointIndex() != INVALID_JOINT_INDEX &&
(entity->getParentID() == DependencyManager::get<NodeList>()->getSessionUUID() || entity->getParentID() == DependencyManager::get<AvatarManager>()->getMyAvatar()->getSelfID());
return entity->isVisible() && (entity->getParentJointIndex() != INVALID_JOINT_INDEX || (entity->getType() == EntityTypes::Model && (std::static_pointer_cast<ModelEntityItem>(entity))->getRelayParentJoints()))
&& (entity->getParentID() == DependencyManager::get<NodeList>()->getSessionUUID() || entity->getParentID() == DependencyManager::get<AvatarManager>()->getMyAvatar()->getSelfID());
}
void AvatarBookmarks::updateAvatarEntities(const QVariantList &avatarEntities) {
@ -254,7 +255,6 @@ QVariantMap AvatarBookmarks::getAvatarDataToBookmark() {
bookmark.insert(ENTRY_VERSION, AVATAR_BOOKMARK_VERSION);
bookmark.insert(ENTRY_AVATAR_URL, avatarUrl);
bookmark.insert(ENTRY_AVATAR_SCALE, avatarScale);
bookmark.insert(ENTRY_AVATAR_ATTACHMENTS, myAvatar->getAttachmentsVariant());
QScriptEngine scriptEngine;
QVariantList wearableEntities;

View file

@ -277,13 +277,6 @@ Menu::Menu() {
QString("hifi/tablet/TabletGraphicsPreferences.qml"), "GraphicsPreferencesDialog");
});
// Settings > Attachments...
action = addActionToQMenuAndActionHash(settingsMenu, MenuOption::Attachments);
connect(action, &QAction::triggered, [] {
qApp->showDialog(QString("hifi/dialogs/AttachmentsDialog.qml"),
QString("hifi/tablet/TabletAttachmentsDialog.qml"), "AttachmentsDialog");
});
// Settings > Developer Menu
addCheckableActionToQMenuAndActionHash(settingsMenu, "Developer Menu", 0, false, this, SLOT(toggleDeveloperMenus()));

View file

@ -36,7 +36,6 @@ namespace MenuOption {
const QString AskToResetSettings = "Ask To Reset Settings on Start";
const QString AssetMigration = "ATP Asset Migration";
const QString AssetServer = "Asset Browser";
const QString Attachments = "Attachments...";
const QString AudioScope = "Show Scope";
const QString AudioScopeFiftyFrames = "Fifty";
const QString AudioScopeFiveFrames = "Five";

View file

@ -20,7 +20,6 @@ Script.include("/~/system/libraries/controllers.js");
// constants from AvatarBookmarks.h
var ENTRY_AVATAR_URL = "avatarUrl";
var ENTRY_AVATAR_ATTACHMENTS = "attachments";
var ENTRY_AVATAR_ENTITIES = "avatarEntites";
var ENTRY_AVATAR_SCALE = "avatarScale";
var ENTRY_VERSION = "version";
@ -31,7 +30,7 @@ function executeLater(callback) {
var INVALID_JOINT_INDEX = -1
function isWearable(avatarEntity) {
return avatarEntity.properties.visible === true && avatarEntity.properties.parentJointIndex !== INVALID_JOINT_INDEX &&
return avatarEntity.properties.visible === true && (avatarEntity.properties.parentJointIndex !== INVALID_JOINT_INDEX || avatarEntity.properties.relayParentJoints === true) &&
(avatarEntity.properties.parentID === MyAvatar.sessionUUID || avatarEntity.properties.parentID === MyAvatar.SELF_ID);
}
@ -57,7 +56,6 @@ function getMyAvatar() {
var avatar = {}
avatar[ENTRY_AVATAR_URL] = MyAvatar.skeletonModelURL;
avatar[ENTRY_AVATAR_SCALE] = MyAvatar.getAvatarScale();
avatar[ENTRY_AVATAR_ATTACHMENTS] = MyAvatar.getAttachmentsVariant();
avatar[ENTRY_AVATAR_ENTITIES] = getMyAvatarWearables();
return avatar;
}
@ -72,12 +70,15 @@ function getMyAvatarSettings() {
}
}
function updateAvatarWearables(avatar, bookmarkAvatarName) {
function updateAvatarWearables(avatar, bookmarkAvatarName, callback) {
executeLater(function() {
var wearables = getMyAvatarWearables();
avatar[ENTRY_AVATAR_ENTITIES] = wearables;
sendToQml({'method' : 'wearablesUpdated', 'wearables' : wearables, 'avatarName' : bookmarkAvatarName})
if(callback)
callback();
});
}
@ -235,6 +236,32 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See
Messages.messageReceived.disconnect(handleWearableMessages);
Messages.unsubscribe('Hifi-Object-Manipulation');
break;
case 'addWearable':
var joints = MyAvatar.getJointNames();
var hipsIndex = -1;
for(var i = 0; i < joints.length; ++i) {
if(joints[i] === 'Hips') {
hipsIndex = i;
break;
}
}
var properties = {
name: "Custom wearable",
type: "Model",
modelURL: message.url,
parentID: MyAvatar.sessionUUID,
relayParentJoints: false,
parentJointIndex: hipsIndex
};
var entityID = Entities.addEntity(properties, true);
updateAvatarWearables(currentAvatar, message.avatarName, function() {
onSelectedEntity(entityID);
});
break;
case 'selectWearable':
ensureWearableSelected(message.entityID);
break;