mirror of
https://github.com/JulianGro/overte.git
synced 2025-04-17 22:08:27 +02:00
Update from zfox23/PALv2 branch
This commit is contained in:
parent
49efd8ad42
commit
beb848373b
11 changed files with 1681 additions and 553 deletions
BIN
interface/resources/icons/profilePicLoading.gif
Normal file
BIN
interface/resources/icons/profilePicLoading.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
|
@ -88,7 +88,7 @@ Original.CheckBox {
|
|||
label: Label {
|
||||
text: control.text
|
||||
colorScheme: checkBox.colorScheme
|
||||
x: checkBox.boxSize / 2
|
||||
x: 2
|
||||
wrapMode: Text.Wrap
|
||||
enabled: checkBox.enabled
|
||||
}
|
||||
|
|
|
@ -48,11 +48,12 @@ TableView {
|
|||
HiFiGlyphs {
|
||||
id: titleSort
|
||||
text: sortIndicatorOrder == Qt.AscendingOrder ? hifi.glyphs.caratUp : hifi.glyphs.caratDn
|
||||
color: hifi.colors.baseGrayHighlight
|
||||
color: hifi.colors.darkGray
|
||||
opacity: 0.6;
|
||||
size: hifi.fontSizes.tableHeadingIcon
|
||||
anchors {
|
||||
left: titleText.right
|
||||
leftMargin: -hifi.fontSizes.tableHeadingIcon / 3 - (centerHeaderText ? 3 : 0)
|
||||
leftMargin: -hifi.fontSizes.tableHeadingIcon / 3 - (centerHeaderText ? 5 : 0)
|
||||
right: parent.right
|
||||
rightMargin: hifi.dimensions.tablePadding
|
||||
verticalCenter: titleText.verticalCenter
|
||||
|
@ -89,7 +90,7 @@ TableView {
|
|||
Rectangle {
|
||||
color: "#00000000"
|
||||
anchors { fill: parent; margins: -2 }
|
||||
radius: hifi.dimensions.borderRadius
|
||||
//radius: hifi.dimensions.borderRadius
|
||||
border.color: isLightColorScheme ? hifi.colors.lightGrayText : hifi.colors.baseGrayHighlight
|
||||
border.width: 2
|
||||
}
|
||||
|
|
|
@ -183,7 +183,7 @@ FocusScope {
|
|||
anchors.leftMargin: hifi.dimensions.textPadding
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
id: popupText
|
||||
text: listView.model[index] ? listView.model[index] : ""
|
||||
text: listView.model[index] ? listView.model[index] : (listView.model.get(index).text ? listView.model.get(index).text : "")
|
||||
size: hifi.fontSizes.textFieldInput
|
||||
color: hifi.colors.baseGray
|
||||
}
|
||||
|
|
|
@ -14,388 +14,453 @@ import QtQuick.Controls 1.4
|
|||
import QtQuick.Controls.Styles 1.4
|
||||
import QtGraphicalEffects 1.0
|
||||
import "../styles-uit"
|
||||
import "toolbars"
|
||||
|
||||
Item {
|
||||
id: thisNameCard
|
||||
// Anchors
|
||||
anchors {
|
||||
verticalCenter: parent.verticalCenter
|
||||
leftMargin: 10
|
||||
rightMargin: 10
|
||||
}
|
||||
// Size
|
||||
width: isMyCard ? pal.myCardWidth - anchors.leftMargin : pal.nearbyNameCardWidth;
|
||||
height: isMyCard ? pal.myCardHeight : pal.rowHeight;
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 5
|
||||
anchors.top: parent.top;
|
||||
|
||||
// Properties
|
||||
property string profileUrl: "";
|
||||
property string defaultBaseUrl: "http://highfidelity.com";
|
||||
property string connectionStatus : ""
|
||||
property string uuid: ""
|
||||
property string displayName: ""
|
||||
property string userName: ""
|
||||
property real displayNameTextPixelSize: 18
|
||||
property int usernameTextHeight: 12
|
||||
property int usernameTextPixelSize: 14
|
||||
property real audioLevel: 0.0
|
||||
property real avgAudioLevel: 0.0
|
||||
property bool isMyCard: false
|
||||
property bool selected: false
|
||||
property bool isAdmin: false
|
||||
property bool currentlyEditingDisplayName: false
|
||||
property string imageMaskColor: pal.color;
|
||||
property string profilePicBorderColor: (connectionStatus == "connection" ? hifi.colors.indigoAccent : (connectionStatus == "friend" ? hifi.colors.greenHighlight : imageMaskColor))
|
||||
|
||||
/* User image commented out for now - will probably be re-introduced later.
|
||||
Column {
|
||||
Item {
|
||||
id: avatarImage
|
||||
visible: profileUrl !== "";
|
||||
// Size
|
||||
height: parent.height
|
||||
width: height
|
||||
height: isMyCard ? 70 : 42;
|
||||
width: visible ? height : 0;
|
||||
anchors.top: parent.top;
|
||||
anchors.topMargin: isMyCard ? 0 : 8;
|
||||
anchors.left: parent.left
|
||||
clip: true
|
||||
Image {
|
||||
id: userImage
|
||||
source: "../../icons/defaultNameCardUser.png"
|
||||
source: profileUrl !== "" ? ((0 === profileUrl.indexOf("http")) ? profileUrl : (defaultBaseUrl + profileUrl)) : "";
|
||||
mipmap: true;
|
||||
// Anchors
|
||||
anchors.fill: parent
|
||||
}
|
||||
AnimatedImage {
|
||||
source: "../../icons/profilePicLoading.gif"
|
||||
anchors.fill: parent;
|
||||
visible: userImage.status != Image.Ready;
|
||||
}
|
||||
// Circular mask
|
||||
Rectangle {
|
||||
id: avatarImageMask;
|
||||
visible: avatarImage.visible;
|
||||
anchors.verticalCenter: avatarImage.verticalCenter;
|
||||
anchors.horizontalCenter: avatarImage.horizontalCenter;
|
||||
width: avatarImage.width * 2;
|
||||
height: avatarImage.height * 2;
|
||||
color: "transparent"
|
||||
radius: avatarImage.height;
|
||||
border.color: imageMaskColor;
|
||||
border.width: avatarImage.height/2;
|
||||
}
|
||||
StateImage {
|
||||
id: infoHoverImage;
|
||||
visible: avatarImageMouseArea.containsMouse ? true : false;
|
||||
imageURL: "../../images/info-icon-2-state.svg";
|
||||
size: 32;
|
||||
buttonState: 1;
|
||||
anchors.centerIn: parent;
|
||||
}
|
||||
MouseArea {
|
||||
id: avatarImageMouseArea;
|
||||
anchors.fill: parent
|
||||
enabled: selected || isMyCard;
|
||||
hoverEnabled: enabled
|
||||
onClicked: {
|
||||
/*
|
||||
THIS WILL OPEN THE BROWSER TO THE USER'S INFO PAGE!
|
||||
I've no idea how to do this yet..
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Colored border around avatarImage
|
||||
Rectangle {
|
||||
id: avatarImageBorder;
|
||||
visible: avatarImage.visible;
|
||||
anchors.verticalCenter: avatarImage.verticalCenter;
|
||||
anchors.horizontalCenter: avatarImage.horizontalCenter;
|
||||
width: avatarImage.width + border.width;
|
||||
height: avatarImage.height + border.width;
|
||||
color: "transparent"
|
||||
radius: avatarImage.height;
|
||||
border.color: profilePicBorderColor;
|
||||
border.width: 4;
|
||||
}
|
||||
|
||||
// DisplayName field for my card
|
||||
Rectangle {
|
||||
id: myDisplayName
|
||||
visible: isMyCard
|
||||
// Size
|
||||
width: parent.width - avatarImage.width - anchors.leftMargin*2 - anchors.rightMargin;
|
||||
height: 40
|
||||
// Anchors
|
||||
anchors.top: avatarImage.top
|
||||
anchors.left: avatarImage.right
|
||||
anchors.leftMargin: 5;
|
||||
anchors.rightMargin: 5;
|
||||
// Style
|
||||
color: myDisplayNameMouseArea.containsMouse ? hifi.colors.lightGrayText : hifi.colors.textFieldLightBackground
|
||||
border.color: hifi.colors.blueHighlight
|
||||
border.width: 0
|
||||
TextInput {
|
||||
id: myDisplayNameText
|
||||
// Properties
|
||||
text: thisNameCard.displayName
|
||||
maximumLength: 256
|
||||
clip: true
|
||||
// Size
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
}
|
||||
}
|
||||
*/
|
||||
Item {
|
||||
id: textContainer
|
||||
// Size
|
||||
width: parent.width - /*avatarImage.width - parent.spacing - */parent.anchors.leftMargin - parent.anchors.rightMargin
|
||||
height: selected || isMyCard ? childrenRect.height : childrenRect.height - 15
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
// DisplayName field for my card
|
||||
Rectangle {
|
||||
id: myDisplayName
|
||||
visible: isMyCard
|
||||
// Size
|
||||
width: parent.width + 70
|
||||
height: 35
|
||||
// Anchors
|
||||
anchors.top: parent.top
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: -10
|
||||
anchors.leftMargin: 10
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: editGlyph.width + editGlyph.anchors.rightMargin
|
||||
// Style
|
||||
color: hifi.colors.textFieldLightBackground
|
||||
border.color: hifi.colors.blueHighlight
|
||||
border.width: 0
|
||||
TextInput {
|
||||
id: myDisplayNameText
|
||||
// Properties
|
||||
text: thisNameCard.displayName
|
||||
maximumLength: 256
|
||||
clip: true
|
||||
// Size
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
// Anchors
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 10
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: editGlyph.width + editGlyph.anchors.rightMargin
|
||||
// Style
|
||||
color: hifi.colors.darkGray
|
||||
FontLoader { id: firaSansSemiBold; source: "../../fonts/FiraSans-SemiBold.ttf"; }
|
||||
font.family: firaSansSemiBold.name
|
||||
font.pixelSize: displayNameTextPixelSize
|
||||
selectionColor: hifi.colors.blueHighlight
|
||||
selectedTextColor: "black"
|
||||
// Text Positioning
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
horizontalAlignment: TextInput.AlignLeft
|
||||
// Signals
|
||||
onEditingFinished: {
|
||||
pal.sendToScript({method: 'displayNameUpdate', params: text})
|
||||
cursorPosition = 0
|
||||
focus = false
|
||||
myDisplayName.border.width = 0
|
||||
color = hifi.colors.darkGray
|
||||
currentlyEditingDisplayName = false
|
||||
}
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
myDisplayName.border.width = 1
|
||||
myDisplayNameText.focus ? myDisplayNameText.cursorPosition = myDisplayNameText.positionAt(mouseX, mouseY, TextInput.CursorOnCharacter) : myDisplayNameText.selectAll();
|
||||
myDisplayNameText.focus = true
|
||||
myDisplayNameText.color = "black"
|
||||
currentlyEditingDisplayName = true
|
||||
}
|
||||
onDoubleClicked: {
|
||||
myDisplayNameText.selectAll();
|
||||
myDisplayNameText.focus = true;
|
||||
currentlyEditingDisplayName = true
|
||||
}
|
||||
onEntered: myDisplayName.color = hifi.colors.lightGrayText
|
||||
onExited: myDisplayName.color = hifi.colors.textFieldLightBackground
|
||||
}
|
||||
// Edit pencil glyph
|
||||
HiFiGlyphs {
|
||||
id: editGlyph
|
||||
text: hifi.glyphs.editPencil
|
||||
// Text Size
|
||||
size: displayNameTextPixelSize*1.5
|
||||
// Anchors
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 5
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
// Style
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: hifi.colors.baseGray
|
||||
}
|
||||
}
|
||||
// Spacer for DisplayName for my card
|
||||
Item {
|
||||
id: myDisplayNameSpacer
|
||||
width: 1
|
||||
height: 4
|
||||
// Anchors
|
||||
anchors.top: myDisplayName.bottom
|
||||
}
|
||||
// DisplayName container for others' cards
|
||||
Item {
|
||||
id: displayNameContainer
|
||||
visible: !isMyCard
|
||||
// Size
|
||||
width: parent.width
|
||||
height: displayNameTextPixelSize + 4
|
||||
// Anchors
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
// DisplayName Text for others' cards
|
||||
FiraSansSemiBold {
|
||||
id: displayNameText
|
||||
// Properties
|
||||
text: thisNameCard.displayName
|
||||
elide: Text.ElideRight
|
||||
// Size
|
||||
width: isAdmin ? Math.min(displayNameTextMetrics.tightBoundingRect.width + 8, parent.width - adminLabelText.width - adminLabelQuestionMark.width + 8) : parent.width
|
||||
// Anchors
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
// Text Size
|
||||
size: displayNameTextPixelSize
|
||||
// Text Positioning
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
// Style
|
||||
color: hifi.colors.darkGray
|
||||
}
|
||||
TextMetrics {
|
||||
id: displayNameTextMetrics
|
||||
font: displayNameText.font
|
||||
text: displayNameText.text
|
||||
}
|
||||
// "ADMIN" label for other users' cards
|
||||
RalewaySemiBold {
|
||||
id: adminLabelText
|
||||
visible: isAdmin
|
||||
text: "ADMIN"
|
||||
// Text size
|
||||
size: displayNameText.size - 4
|
||||
// Anchors
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: displayNameText.right
|
||||
// Style
|
||||
font.capitalization: Font.AllUppercase
|
||||
color: hifi.colors.redHighlight
|
||||
// Alignment
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignTop
|
||||
}
|
||||
// This Rectangle refers to the [?] popup button next to "ADMIN"
|
||||
Item {
|
||||
id: adminLabelQuestionMark
|
||||
visible: isAdmin
|
||||
// Size
|
||||
width: 20
|
||||
height: displayNameText.height
|
||||
// Anchors
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: adminLabelText.right
|
||||
RalewayRegular {
|
||||
id: adminLabelQuestionMarkText
|
||||
text: "[?]"
|
||||
size: adminLabelText.size
|
||||
font.capitalization: Font.AllUppercase
|
||||
color: hifi.colors.redHighlight
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
anchors.fill: parent
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton
|
||||
hoverEnabled: true
|
||||
onClicked: letterbox(hifi.glyphs.question,
|
||||
"Domain Admin",
|
||||
"This user is an admin on this domain. Admins can <b>Silence</b> and <b>Ban</b> other users at their discretion - so be extra nice!")
|
||||
onEntered: adminLabelQuestionMarkText.color = "#94132e"
|
||||
onExited: adminLabelQuestionMarkText.color = hifi.colors.redHighlight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UserName Text
|
||||
FiraSansRegular {
|
||||
id: userNameText
|
||||
// Properties
|
||||
text: thisNameCard.userName
|
||||
elide: Text.ElideRight
|
||||
visible: thisNameCard.displayName
|
||||
// Size
|
||||
width: parent.width
|
||||
// Anchors
|
||||
anchors.top: isMyCard ? myDisplayNameSpacer.bottom : displayNameContainer.bottom
|
||||
// Text Size
|
||||
size: thisNameCard.usernameTextHeight
|
||||
color: hifi.colors.darkGray
|
||||
FontLoader { id: firaSansSemiBold; source: "../../fonts/FiraSans-SemiBold.ttf"; }
|
||||
font.family: firaSansSemiBold.name
|
||||
font.pixelSize: displayNameTextPixelSize
|
||||
selectionColor: hifi.colors.blueHighlight
|
||||
selectedTextColor: "black"
|
||||
// Text Positioning
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
horizontalAlignment: TextInput.AlignLeft
|
||||
autoScroll: false;
|
||||
// Signals
|
||||
onEditingFinished: {
|
||||
pal.sendToScript({method: 'displayNameUpdate', params: text})
|
||||
cursorPosition = 0
|
||||
focus = false
|
||||
myDisplayName.border.width = 0
|
||||
color = hifi.colors.darkGray
|
||||
pal.currentlyEditingDisplayName = false
|
||||
autoScroll = false;
|
||||
}
|
||||
}
|
||||
MouseArea {
|
||||
id: myDisplayNameMouseArea;
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
myDisplayName.border.width = 1
|
||||
myDisplayNameText.focus ? myDisplayNameText.cursorPosition = myDisplayNameText.positionAt(mouseX, mouseY, TextInput.CursorOnCharacter) : myDisplayNameText.selectAll();
|
||||
myDisplayNameText.focus = true
|
||||
myDisplayNameText.color = "black"
|
||||
pal.currentlyEditingDisplayName = true
|
||||
myDisplayNameText.autoScroll = true;
|
||||
}
|
||||
onDoubleClicked: {
|
||||
myDisplayNameText.selectAll();
|
||||
myDisplayNameText.focus = true;
|
||||
pal.currentlyEditingDisplayName = true
|
||||
myDisplayNameText.autoScroll = true;
|
||||
}
|
||||
}
|
||||
// Edit pencil glyph
|
||||
HiFiGlyphs {
|
||||
id: editGlyph
|
||||
text: hifi.glyphs.editPencil
|
||||
// Text Size
|
||||
size: displayNameTextPixelSize*1.5
|
||||
// Anchors
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 5
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
// Style
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: hifi.colors.baseGray
|
||||
}
|
||||
|
||||
// Spacer
|
||||
Item {
|
||||
id: userNameSpacer
|
||||
height: 4
|
||||
width: parent.width
|
||||
// Anchors
|
||||
anchors.top: userNameText.bottom
|
||||
}
|
||||
|
||||
// VU Meter
|
||||
Rectangle {
|
||||
id: nameCardVUMeter
|
||||
// Size
|
||||
width: isMyCard ? myDisplayName.width - 70 : ((gainSlider.value - gainSlider.minimumValue)/(gainSlider.maximumValue - gainSlider.minimumValue)) * parent.width
|
||||
height: 8
|
||||
// Anchors
|
||||
anchors.top: userNameSpacer.bottom
|
||||
// Style
|
||||
radius: 4
|
||||
color: "#c5c5c5"
|
||||
visible: isMyCard || selected
|
||||
// Rectangle for the zero-gain point on the VU meter
|
||||
Rectangle {
|
||||
id: vuMeterZeroGain
|
||||
visible: gainSlider.visible
|
||||
// Size
|
||||
width: 4
|
||||
height: 18
|
||||
// Style
|
||||
color: hifi.colors.darkGray
|
||||
// Anchors
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: (-gainSlider.minimumValue)/(gainSlider.maximumValue - gainSlider.minimumValue) * gainSlider.width - 4
|
||||
}
|
||||
// Rectangle for the VU meter line
|
||||
Rectangle {
|
||||
id: vuMeterLine
|
||||
width: gainSlider.width
|
||||
visible: gainSlider.visible
|
||||
// Style
|
||||
color: vuMeterBase.color
|
||||
radius: nameCardVUMeter.radius
|
||||
height: nameCardVUMeter.height / 2
|
||||
anchors.verticalCenter: nameCardVUMeter.verticalCenter
|
||||
}
|
||||
// Rectangle for the VU meter base
|
||||
Rectangle {
|
||||
id: vuMeterBase
|
||||
// Anchors
|
||||
anchors.fill: parent
|
||||
visible: isMyCard || selected
|
||||
// Style
|
||||
color: parent.color
|
||||
radius: parent.radius
|
||||
}
|
||||
// Rectangle for the VU meter audio level
|
||||
Rectangle {
|
||||
id: vuMeterLevel
|
||||
visible: isMyCard || selected
|
||||
// Size
|
||||
width: (thisNameCard.audioLevel) * parent.width
|
||||
// Style
|
||||
color: parent.color
|
||||
radius: parent.radius
|
||||
// Anchors
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
}
|
||||
// Gradient for the VU meter audio level
|
||||
LinearGradient {
|
||||
anchors.fill: vuMeterLevel
|
||||
source: vuMeterLevel
|
||||
start: Qt.point(0, 0)
|
||||
end: Qt.point(parent.width, 0)
|
||||
gradient: Gradient {
|
||||
GradientStop { position: 0.0; color: "#2c8e72" }
|
||||
GradientStop { position: 0.9; color: "#1fc6a6" }
|
||||
GradientStop { position: 0.91; color: "#ea4c5f" }
|
||||
GradientStop { position: 1.0; color: "#ea4c5f" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Per-Avatar Gain Slider
|
||||
Slider {
|
||||
id: gainSlider
|
||||
// Size
|
||||
width: parent.width
|
||||
height: 14
|
||||
// Anchors
|
||||
anchors.verticalCenter: nameCardVUMeter.verticalCenter
|
||||
}
|
||||
// DisplayName container for others' cards
|
||||
Item {
|
||||
id: displayNameContainer
|
||||
visible: !isMyCard
|
||||
// Size
|
||||
width: parent.width - anchors.leftMargin - avatarImage.width - anchors.leftMargin;
|
||||
height: displayNameTextPixelSize + 4
|
||||
// Anchors
|
||||
anchors.top: pal.activeTab == "connectionsTab" ? undefined : avatarImage.top;
|
||||
anchors.bottom: pal.activeTab == "connectionsTab" ? avatarImage.bottom : undefined;
|
||||
anchors.left: avatarImage.right
|
||||
anchors.leftMargin: avatarImage.visible ? 5 : 0;
|
||||
// DisplayName Text for others' cards
|
||||
FiraSansSemiBold {
|
||||
id: displayNameText
|
||||
// Properties
|
||||
visible: !isMyCard && selected
|
||||
value: Users.getAvatarGain(uuid)
|
||||
minimumValue: -60.0
|
||||
maximumValue: 20.0
|
||||
stepSize: 5
|
||||
updateValueWhileDragging: true
|
||||
onValueChanged: updateGainFromQML(uuid, value, false)
|
||||
onPressedChanged: {
|
||||
if (!pressed) {
|
||||
updateGainFromQML(uuid, value, true)
|
||||
}
|
||||
text: thisNameCard.displayName
|
||||
elide: Text.ElideRight
|
||||
// Size
|
||||
width: isAdmin ? Math.min(displayNameTextMetrics.tightBoundingRect.width + 8, parent.width - adminLabelText.width - adminLabelQuestionMark.width + 8) : parent.width
|
||||
// Anchors
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
// Text Size
|
||||
size: displayNameTextPixelSize
|
||||
// Text Positioning
|
||||
verticalAlignment: Text.AlignTop
|
||||
// Style
|
||||
color: (pal.activeTab == "nearbyTab" && (displayNameTextMouseArea.containsMouse || userNameTextMouseArea.containsMouse))
|
||||
? hifi.colors.blueHighlight : (pal.activeTab == "nearbyTab" ? hifi.colors.darkGray : hifi.colors.greenShadow);
|
||||
MouseArea {
|
||||
id: displayNameTextMouseArea;
|
||||
anchors.fill: parent
|
||||
enabled: selected && pal.activeTab == "nearbyTab" && thisNameCard.userName !== "";
|
||||
hoverEnabled: enabled
|
||||
onClicked: pal.sendToScript({method: 'goToUser', params: thisNameCard.userName});
|
||||
}
|
||||
}
|
||||
TextMetrics {
|
||||
id: displayNameTextMetrics
|
||||
font: displayNameText.font
|
||||
text: displayNameText.text
|
||||
}
|
||||
// "ADMIN" label for other users' cards
|
||||
RalewaySemiBold {
|
||||
id: adminLabelText
|
||||
visible: isAdmin
|
||||
text: "ADMIN"
|
||||
// Text size
|
||||
size: displayNameText.size - 4
|
||||
// Anchors
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: displayNameText.right
|
||||
// Style
|
||||
font.capitalization: Font.AllUppercase
|
||||
color: hifi.colors.redHighlight
|
||||
// Alignment
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignTop
|
||||
}
|
||||
// This Rectangle refers to the [?] popup button next to "ADMIN"
|
||||
Item {
|
||||
id: adminLabelQuestionMark
|
||||
visible: isAdmin
|
||||
// Size
|
||||
width: 20
|
||||
height: displayNameText.height
|
||||
// Anchors
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: adminLabelText.right
|
||||
RalewayRegular {
|
||||
id: adminLabelQuestionMarkText
|
||||
text: "[?]"
|
||||
size: adminLabelText.size
|
||||
font.capitalization: Font.AllUppercase
|
||||
color: hifi.colors.redHighlight
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
anchors.fill: parent
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onWheel: {
|
||||
// Do nothing.
|
||||
}
|
||||
onDoubleClicked: {
|
||||
gainSlider.value = 0.0
|
||||
}
|
||||
onPressed: {
|
||||
// Pass through to Slider
|
||||
mouse.accepted = false
|
||||
}
|
||||
onReleased: {
|
||||
// the above mouse.accepted seems to make this
|
||||
// never get called, nonetheless...
|
||||
mouse.accepted = false
|
||||
}
|
||||
}
|
||||
style: SliderStyle {
|
||||
groove: Rectangle {
|
||||
color: "#c5c5c5"
|
||||
implicitWidth: gainSlider.width
|
||||
implicitHeight: 4
|
||||
radius: 2
|
||||
opacity: 0
|
||||
}
|
||||
handle: Rectangle {
|
||||
anchors.centerIn: parent
|
||||
color: (control.pressed || control.hovered) ? "#00b4ef" : "#8F8F8F"
|
||||
implicitWidth: 10
|
||||
implicitHeight: 16
|
||||
}
|
||||
hoverEnabled: true
|
||||
onClicked: letterbox(hifi.glyphs.question,
|
||||
"Domain Admin",
|
||||
"This user is an admin on this domain. Admins can <b>Silence</b> and <b>Ban</b> other users at their discretion - so be extra nice!")
|
||||
onEntered: adminLabelQuestionMarkText.color = "#94132e"
|
||||
onExited: adminLabelQuestionMarkText.color = hifi.colors.redHighlight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UserName Text
|
||||
FiraSansRegular {
|
||||
id: userNameText
|
||||
// Properties
|
||||
text: thisNameCard.userName
|
||||
elide: Text.ElideRight
|
||||
visible: thisNameCard.displayName
|
||||
// Size
|
||||
width: parent.width
|
||||
height: usernameTextPixelSize + 4
|
||||
// Anchors
|
||||
anchors.top: isMyCard ? myDisplayName.bottom : undefined;
|
||||
anchors.bottom: isMyCard ? undefined : avatarImage.bottom
|
||||
anchors.left: avatarImage.right;
|
||||
anchors.leftMargin: avatarImage.visible ? 5 : 0;
|
||||
// Text Size
|
||||
size: usernameTextPixelSize;
|
||||
// Text Positioning
|
||||
verticalAlignment: Text.AlignBottom
|
||||
// Style
|
||||
color: (pal.activeTab == "nearbyTab" && (displayNameTextMouseArea.containsMouse || userNameTextMouseArea.containsMouse)) ? hifi.colors.blueHighlight : hifi.colors.greenShadow;
|
||||
MouseArea {
|
||||
id: userNameTextMouseArea;
|
||||
anchors.fill: parent
|
||||
enabled: selected && pal.activeTab == "nearbyTab" && thisNameCard.userName !== "";
|
||||
hoverEnabled: enabled
|
||||
onClicked: pal.sendToScript({method: 'goToUser', params: thisNameCard.userName});
|
||||
}
|
||||
}
|
||||
// VU Meter
|
||||
Rectangle {
|
||||
id: nameCardVUMeter
|
||||
// Size
|
||||
width: isMyCard ? myDisplayName.width - 20 : ((gainSlider.value - gainSlider.minimumValue)/(gainSlider.maximumValue - gainSlider.minimumValue)) * (gainSlider.width);
|
||||
height: 8
|
||||
// Anchors
|
||||
anchors.bottom: isMyCard ? avatarImage.bottom : parent.bottom;
|
||||
anchors.bottomMargin: isMyCard ? 0 : height;
|
||||
anchors.left: isMyCard ? userNameText.left : parent.left;
|
||||
// Style
|
||||
radius: 4
|
||||
color: "#c5c5c5"
|
||||
visible: isMyCard || (selected && pal.activeTab == "nearbyTab")
|
||||
// Rectangle for the zero-gain point on the VU meter
|
||||
Rectangle {
|
||||
id: vuMeterZeroGain
|
||||
visible: gainSlider.visible
|
||||
// Size
|
||||
width: 4
|
||||
height: 18
|
||||
// Style
|
||||
color: hifi.colors.darkGray
|
||||
// Anchors
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: (-gainSlider.minimumValue)/(gainSlider.maximumValue - gainSlider.minimumValue) * gainSlider.width - 4
|
||||
}
|
||||
// Rectangle for the VU meter line
|
||||
Rectangle {
|
||||
id: vuMeterLine
|
||||
width: gainSlider.width
|
||||
visible: gainSlider.visible
|
||||
// Style
|
||||
color: vuMeterBase.color
|
||||
radius: nameCardVUMeter.radius
|
||||
height: nameCardVUMeter.height / 2
|
||||
anchors.verticalCenter: nameCardVUMeter.verticalCenter
|
||||
}
|
||||
// Rectangle for the VU meter base
|
||||
Rectangle {
|
||||
id: vuMeterBase
|
||||
// Anchors
|
||||
anchors.fill: parent
|
||||
visible: isMyCard || selected
|
||||
// Style
|
||||
color: parent.color
|
||||
radius: parent.radius
|
||||
}
|
||||
// Rectangle for the VU meter audio level
|
||||
Rectangle {
|
||||
id: vuMeterLevel
|
||||
visible: isMyCard || selected
|
||||
// Size
|
||||
width: (thisNameCard.audioLevel) * parent.width
|
||||
// Style
|
||||
color: parent.color
|
||||
radius: parent.radius
|
||||
// Anchors
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
}
|
||||
// Gradient for the VU meter audio level
|
||||
LinearGradient {
|
||||
anchors.fill: vuMeterLevel
|
||||
source: vuMeterLevel
|
||||
start: Qt.point(0, 0)
|
||||
end: Qt.point(parent.width, 0)
|
||||
gradient: Gradient {
|
||||
GradientStop { position: 0.0; color: "#2c8e72" }
|
||||
GradientStop { position: 0.9; color: "#1fc6a6" }
|
||||
GradientStop { position: 0.91; color: "#ea4c5f" }
|
||||
GradientStop { position: 1.0; color: "#ea4c5f" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Per-Avatar Gain Slider
|
||||
Slider {
|
||||
id: gainSlider
|
||||
// Size
|
||||
width: thisNameCard.width;
|
||||
height: 14
|
||||
// Anchors
|
||||
anchors.verticalCenter: nameCardVUMeter.verticalCenter;
|
||||
anchors.left: nameCardVUMeter.left;
|
||||
// Properties
|
||||
visible: !isMyCard && selected && pal.activeTab == "nearbyTab";
|
||||
value: Users.getAvatarGain(uuid)
|
||||
minimumValue: -60.0
|
||||
maximumValue: 20.0
|
||||
stepSize: 5
|
||||
updateValueWhileDragging: true
|
||||
onValueChanged: {
|
||||
if (uuid !== "") {
|
||||
updateGainFromQML(uuid, value, false);
|
||||
}
|
||||
}
|
||||
onPressedChanged: {
|
||||
if (!pressed) {
|
||||
updateGainFromQML(uuid, value, true)
|
||||
}
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onWheel: {
|
||||
// Do nothing.
|
||||
}
|
||||
onDoubleClicked: {
|
||||
gainSlider.value = 0.0
|
||||
}
|
||||
onPressed: {
|
||||
// Pass through to Slider
|
||||
mouse.accepted = false
|
||||
}
|
||||
onReleased: {
|
||||
// the above mouse.accepted seems to make this
|
||||
// never get called, nonetheless...
|
||||
mouse.accepted = false
|
||||
}
|
||||
}
|
||||
style: SliderStyle {
|
||||
groove: Rectangle {
|
||||
color: "#c5c5c5"
|
||||
implicitWidth: gainSlider.width
|
||||
implicitHeight: 4
|
||||
radius: 2
|
||||
opacity: 0
|
||||
}
|
||||
handle: Rectangle {
|
||||
anchors.centerIn: parent
|
||||
color: (control.pressed || control.hovered) ? "#00b4ef" : "#8F8F8F"
|
||||
implicitWidth: 10
|
||||
implicitHeight: 16
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateGainFromQML(avatarUuid, sliderValue, isReleased) {
|
||||
Users.setAvatarGain(avatarUuid, sliderValue);
|
||||
if (isReleased) {
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -171,7 +171,7 @@ Item {
|
|||
readonly property real textFieldInputLabel: dimensions.largeScreen ? 13 : 9
|
||||
readonly property real textFieldSearchIcon: dimensions.largeScreen ? 30 : 24
|
||||
readonly property real tableHeading: dimensions.largeScreen ? 12 : 10
|
||||
readonly property real tableHeadingIcon: dimensions.largeScreen ? 40 : 33
|
||||
readonly property real tableHeadingIcon: dimensions.largeScreen ? 60 : 33
|
||||
readonly property real tableText: dimensions.largeScreen ? 15 : 12
|
||||
readonly property real buttonLabel: dimensions.largeScreen ? 13 : 9
|
||||
readonly property real iconButton: dimensions.largeScreen ? 13 : 9
|
||||
|
|
|
@ -27,7 +27,7 @@ class HMDScriptingInterface : public AbstractHMDScriptingInterface, public Depen
|
|||
Q_OBJECT
|
||||
Q_PROPERTY(glm::vec3 position READ getPosition)
|
||||
Q_PROPERTY(glm::quat orientation READ getOrientation)
|
||||
Q_PROPERTY(bool mounted READ isMounted)
|
||||
Q_PROPERTY(bool mounted READ isMounted NOTIFY mountedChanged)
|
||||
Q_PROPERTY(bool showTablet READ getShouldShowTablet)
|
||||
Q_PROPERTY(QUuid tabletID READ getCurrentTabletFrameID WRITE setCurrentTabletFrameID)
|
||||
Q_PROPERTY(QUuid homeButtonID READ getCurrentHomeButtonID WRITE setCurrentHomeButtonID)
|
||||
|
@ -80,6 +80,7 @@ public:
|
|||
|
||||
signals:
|
||||
bool shouldShowHandControllersChanged();
|
||||
void mountedChanged();
|
||||
|
||||
public:
|
||||
HMDScriptingInterface();
|
||||
|
|
|
@ -21,10 +21,10 @@ var DEFAULT_SCRIPTS = [
|
|||
"system/snapshot.js",
|
||||
"system/help.js",
|
||||
"system/pal.js", // "system/mod.js", // older UX, if you prefer
|
||||
"system/makeUserConnection.js",
|
||||
"system/goto.js",
|
||||
"system/marketplaces/marketplaces.js",
|
||||
"system/edit.js",
|
||||
"system/tablet-users.js",
|
||||
"system/selectAudioDevice.js",
|
||||
"system/notifications.js",
|
||||
"system/controllers/controllerDisplayManager.js",
|
||||
|
|
372
scripts/system/makeUserConnection.js
Normal file
372
scripts/system/makeUserConnection.js
Normal file
|
@ -0,0 +1,372 @@
|
|||
"use strict";
|
||||
//
|
||||
// friends.js
|
||||
// scripts/developer/tests/performance/
|
||||
//
|
||||
// Created by David Kelly on 3/7/2017.
|
||||
// Copyright 2017 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
|
||||
//
|
||||
const version = 0.1;
|
||||
const label = "Friends";
|
||||
const MAX_AVATAR_DISTANCE = 1.0;
|
||||
const GRIP_MIN = 0.05;
|
||||
const MESSAGE_CHANNEL = "io.highfidelity.friends";
|
||||
const STATES = {
|
||||
inactive : 0,
|
||||
waiting: 1,
|
||||
friending: 2,
|
||||
};
|
||||
const STATE_STRINGS = ["inactive", "waiting", "friending"];
|
||||
const WAITING_INTERVAL = 100; // ms
|
||||
const FRIENDING_INTERVAL = 100; // ms
|
||||
const FRIENDING_TIME = 3000; // ms
|
||||
const OVERLAY_COLORS = [{red: 0x00, green: 0xFF, blue: 0x00}, {red: 0x00, green: 0x00, blue: 0xFF}];
|
||||
const FRIENDING_HAPTIC_STRENGTH = 0.5;
|
||||
const FRIENDING_SUCCESS_HAPTIC_STRENGTH = 1.0;
|
||||
const HAPTIC_DURATION = 20;
|
||||
|
||||
var currentHand;
|
||||
var isWaiting = false;
|
||||
var nearbyAvatars = [];
|
||||
var state = STATES.inactive;
|
||||
var waitingInterval;
|
||||
var friendingInterval;
|
||||
var entity;
|
||||
var makingFriends = false; // really just for visualizations for now
|
||||
var animHandlerId;
|
||||
var entityDimensionMultiplier = 1.0;
|
||||
|
||||
function debug() {
|
||||
var stateString = "<" + STATE_STRINGS[state] + ">";
|
||||
var versionString = "v" + version;
|
||||
print.apply(null, [].concat.apply([label, versionString, stateString], [].map.call(arguments, JSON.stringify)));
|
||||
}
|
||||
|
||||
function handToString(hand) {
|
||||
if (hand === Controller.Standard.RightHand) {
|
||||
return "RightHand";
|
||||
} else if (hand === Controller.Standard.LeftHand) {
|
||||
return "LeftHand";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function handToHaptic(hand) {
|
||||
if (hand === Controller.Standard.RightHand) {
|
||||
return 1;
|
||||
} else if (hand === Controller.Standard.LeftHand) {
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
function getHandPosition(avatar, hand) {
|
||||
if (!hand) {
|
||||
debug("calling getHandPosition with no hand!");
|
||||
return;
|
||||
}
|
||||
var jointName = handToString(hand) + "Middle1";
|
||||
return avatar.getJointPosition(avatar.getJointIndex(jointName));
|
||||
}
|
||||
|
||||
function shakeHandsAnimation(animationProperties) {
|
||||
// all we are doing here is moving the right hand to a spot
|
||||
// that is in front of and a bit above the hips. Basing how
|
||||
// far in front as scaling with the avatar's height (say hips
|
||||
// to head distance)
|
||||
var headIndex = MyAvatar.getJointIndex("Head");
|
||||
var offset = 0.5; // default distance of hand in front of you
|
||||
var result = {};
|
||||
if (headIndex) {
|
||||
offset = 0.8 * MyAvatar.getAbsoluteJointTranslationInObjectFrame(headIndex).y;
|
||||
}
|
||||
var handPos = Vec3.multiply(offset, {x: -0.25, y: 0.8, z: 1.3});
|
||||
result.rightHandPosition = handPos;
|
||||
result.rightHandRotation = Quat.fromPitchYawRollDegrees(90, 0, 90);
|
||||
return result;
|
||||
}
|
||||
|
||||
// this is called frequently, but usually does nothing
|
||||
function updateVisualization() {
|
||||
if (state == STATES.inactive) {
|
||||
if (entity) {
|
||||
entity = Entities.deleteEntity(entity);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var color = state == STATES.waiting ? OVERLAY_COLORS[0] : OVERLAY_COLORS[1];
|
||||
var position = getHandPosition(MyAvatar, currentHand);
|
||||
|
||||
// temp code, though all of this stuff really is temp...
|
||||
if (makingFriends) {
|
||||
color = { red: 0xFF, green: 0x00, blue: 0x00 };
|
||||
}
|
||||
|
||||
// TODO: make the size scale with avatar, up to
|
||||
// the actual size of MAX_AVATAR_DISTANCE
|
||||
var wrist = MyAvatar.getJointPosition(MyAvatar.getJointIndex(handToString(currentHand)));
|
||||
var d = entityDimensionMultiplier * Vec3.distance(wrist, position);
|
||||
var dimension = {x: d, y: d, z: d};
|
||||
if (!entity) {
|
||||
var props = {
|
||||
type: "Sphere",
|
||||
color: color,
|
||||
position: position,
|
||||
dimensions: dimension
|
||||
}
|
||||
entity = Entities.addEntity(props);
|
||||
} else {
|
||||
Entities.editEntity(entity, {dimensions: dimension, position: position, color: color});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// this should find the nearest avatars, returning an array of avatar, hand pairs. Currently
|
||||
// looking at distance between hands.
|
||||
function findNearbyAvatars() {
|
||||
var nearbyAvatars = [];
|
||||
var handPos = getHandPosition(MyAvatar, currentHand);
|
||||
AvatarList.getAvatarIdentifiers().forEach(function (identifier) {
|
||||
if (!identifier) { return; }
|
||||
var avatar = AvatarList.getAvatar(identifier);
|
||||
var distanceR = Vec3.distance(getHandPosition(avatar, Controller.Standard.RightHand), handPos);
|
||||
var distanceL = Vec3.distance(getHandPosition(avatar, Controller.Standard.LeftHand), handPos);
|
||||
var distance = Math.min(distanceL, distanceR);
|
||||
if (distance < MAX_AVATAR_DISTANCE) {
|
||||
if (distance == distanceR) {
|
||||
nearbyAvatars.push({avatar: identifier, hand: Controller.Standard.RightHand});
|
||||
} else {
|
||||
nearbyAvatars.push({avatar: identifier, hand: Controller.Standard.LeftHand});
|
||||
}
|
||||
}
|
||||
});
|
||||
return nearbyAvatars;
|
||||
}
|
||||
|
||||
function startHandshake(fromKeyboard) {
|
||||
if (fromKeyboard) {
|
||||
debug("adding animation");
|
||||
animHandlerId = MyAvatar.addAnimationStateHandler(shakeHandsAnimation, []);
|
||||
}
|
||||
debug("starting handshake for", currentHand);
|
||||
state = STATES.waiting;
|
||||
waitingInterval = Script.setInterval(
|
||||
function () {
|
||||
debug("currentHand", handToString(currentHand));
|
||||
messageSend({
|
||||
key: "waiting",
|
||||
hand: handToString(currentHand)
|
||||
});
|
||||
}, WAITING_INTERVAL);
|
||||
}
|
||||
|
||||
function endHandshake() {
|
||||
debug("ending handshake for", currentHand);
|
||||
currentHand = undefined;
|
||||
state = STATES.inactive;
|
||||
if (waitingInterval) {
|
||||
waitingInterval = Script.clearInterval(waitingInterval);
|
||||
}
|
||||
if (friendingInterval) {
|
||||
friendingInterval = Script.clearInterval(friendingInterval);
|
||||
}
|
||||
if (animHandlerId) {
|
||||
debug("removing animation");
|
||||
MyAvatar.removeAnimationStateHandler(animHandlerId);
|
||||
}
|
||||
}
|
||||
|
||||
function updateTriggers(value, fromKeyboard, hand) {
|
||||
if (currentHand && hand !== currentHand) {
|
||||
debug("currentHand", currentHand, "ignoring messages from", hand);
|
||||
return;
|
||||
}
|
||||
if (!currentHand) {
|
||||
currentHand = hand;
|
||||
}
|
||||
// ok now, we are either initiating or quitting...
|
||||
var isGripping = value > GRIP_MIN;
|
||||
if (isGripping) {
|
||||
if (state != STATES.inactive) {
|
||||
return;
|
||||
} else {
|
||||
startHandshake(fromKeyboard);
|
||||
}
|
||||
} else {
|
||||
if (state != STATES.inactive) {
|
||||
endHandshake();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function messageSend(message) {
|
||||
Messages.sendMessage(MESSAGE_CHANNEL, JSON.stringify(message));
|
||||
}
|
||||
|
||||
function isNearby(id, hand) {
|
||||
for(var i = 0; i < nearbyAvatars.length; i++) {
|
||||
if (nearbyAvatars[i].avatar == id && handToString(nearbyAvatars[i].hand) == hand) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// this should be where we make the appropriate friend call. For now just make the
|
||||
// visualization change.
|
||||
function makeFriends(id) {
|
||||
// temp code to just flash the visualization really (for now!)
|
||||
makingFriends = true;
|
||||
Controller.triggerHapticPulse(FRIENDING_SUCCESS_HAPTIC_STRENGTH, HAPTIC_DURATION, handToHaptic(currentHand));
|
||||
Script.setTimeout(function () { makingFriends = false; entityDimensionMultiplier = 1.0; }, 1000);
|
||||
}
|
||||
// we change states, start the friendingInterval where we check
|
||||
// to be sure the hand is still close enough. If not, we terminate
|
||||
// the interval, go back to the waiting state. If we make it
|
||||
// the entire FRIENDING_TIME, we make friends.
|
||||
function startFriending(id, hand) {
|
||||
var count = 0;
|
||||
debug("friending", id, "hand", hand);
|
||||
state = STATES.friending;
|
||||
Controller.triggerHapticPulse(FRIENDING_HAPTIC_STRENGTH, HAPTIC_DURATION, handToHaptic(currentHand));
|
||||
if (waitingInterval) {
|
||||
waitingInterval = Script.clearInterval(waitingInterval);
|
||||
}
|
||||
friendingInterval = Script.setInterval(function () {
|
||||
nearbyAvatars = findNearbyAvatars();
|
||||
entityDimensionMultiplier = 1.0 + 2.0 * ++count * FRIENDING_INTERVAL / FRIENDING_TIME;
|
||||
// insure senderID is still nearby
|
||||
if (state != STATES.friending) {
|
||||
debug("stopping friending interval, state changed");
|
||||
friendingInterval = Script.clearInterval(friendingInterval);
|
||||
}
|
||||
if (!isNearby(id, hand)) {
|
||||
// gotta go back to waiting
|
||||
debug(id, "moved, back to waiting");
|
||||
friendingInterval = Script.clearInterval(friendingInterval);
|
||||
startHandshake();
|
||||
} else if (count > FRIENDING_TIME/FRIENDING_INTERVAL) {
|
||||
debug("made friends with " + id);
|
||||
makeFriends(id);
|
||||
friendingInterval = Script.clearInterval(friendingInterval);
|
||||
}
|
||||
}, FRIENDING_INTERVAL);
|
||||
}
|
||||
/*
|
||||
A simple sequence diagram:
|
||||
|
||||
Avatar A Avatar B
|
||||
| |
|
||||
| <---------(waiting) --- startHandshake
|
||||
startHandshake -- (waiting) -----> |
|
||||
| |
|
||||
| <-------(friending) -- startFriending
|
||||
startFriending -- (friending) ---> |
|
||||
| |
|
||||
| friends
|
||||
friends |
|
||||
| ` |
|
||||
*/
|
||||
function messageHandler(channel, messageString, senderID) {
|
||||
if (channel !== MESSAGE_CHANNEL) {
|
||||
return;
|
||||
}
|
||||
if (state == STATES.inactive) {
|
||||
return;
|
||||
}
|
||||
if (MyAvatar.sessionUUID === senderID) { // ignore my own
|
||||
return;
|
||||
}
|
||||
var message = {};
|
||||
try {
|
||||
message = JSON.parse(messageString);
|
||||
} catch (e) {
|
||||
debug(e);
|
||||
}
|
||||
switch (message.key) {
|
||||
case "waiting":
|
||||
case "friending":
|
||||
if (state == STATES.waiting) {
|
||||
if (message.key == "friending" && message.id != MyAvatar.sessionUUID) {
|
||||
// for now, just ignore these. Hmm
|
||||
debug("ignoring friending message", message, "from", senderID);
|
||||
break;
|
||||
}
|
||||
nearbyAvatars = findNearbyAvatars();
|
||||
if (isNearby(senderID, message.hand)) {
|
||||
// if we are responding to a friending message (they didn't send a
|
||||
// waiting before noticing us and friending), don't bother with sending
|
||||
// a friending message?
|
||||
messageSend({
|
||||
key: "friending",
|
||||
id: senderID,
|
||||
hand: handToString(currentHand)
|
||||
});
|
||||
startFriending(senderID, message.hand);
|
||||
} else {
|
||||
// for now, ignore this. Hmm.
|
||||
if (message.key == "friending") {
|
||||
debug(senderID, "is friending us, but not close enough??");
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
debug("unknown message", message);
|
||||
}
|
||||
}
|
||||
|
||||
Messages.subscribe(MESSAGE_CHANNEL);
|
||||
Messages.messageReceived.connect(messageHandler);
|
||||
|
||||
|
||||
function makeGripHandler(hand) {
|
||||
// determine if we are gripping or un-gripping
|
||||
return function (value) {
|
||||
updateTriggers(value, false, hand);
|
||||
};
|
||||
}
|
||||
|
||||
function keyPressEvent(event) {
|
||||
if ((event.text === "x") && !event.isAutoRepeat) {
|
||||
updateTriggers(1.0, true, Controller.Standard.RightHand);
|
||||
}
|
||||
}
|
||||
function keyReleaseEvent(event) {
|
||||
if ((event.text === "x") && !event.isAutoRepeat) {
|
||||
updateTriggers(0.0, true, Controller.Standard.RightHand);
|
||||
}
|
||||
}
|
||||
// map controller actions
|
||||
var friendsMapping = Controller.newMapping(Script.resolvePath('') + '-grip');
|
||||
friendsMapping.from(Controller.Standard.LeftGrip).peek().to(makeGripHandler(Controller.Standard.LeftHand));
|
||||
friendsMapping.from(Controller.Standard.RightGrip).peek().to(makeGripHandler(Controller.Standard.RightHand));
|
||||
|
||||
// setup keyboard initiation
|
||||
|
||||
Controller.keyPressEvent.connect(keyPressEvent);
|
||||
Controller.keyReleaseEvent.connect(keyReleaseEvent);
|
||||
|
||||
// it is easy to forget this and waste a lot of time for nothing
|
||||
friendsMapping.enable();
|
||||
|
||||
// connect updateVisualization to update frequently
|
||||
Script.update.connect(updateVisualization);
|
||||
|
||||
Script.scriptEnding.connect(function () {
|
||||
debug("removing controller mappings");
|
||||
friendsMapping.disable();
|
||||
debug("removing key mappings");
|
||||
Controller.keyPressEvent.disconnect(keyPressEvent);
|
||||
Controller.keyReleaseEvent.disconnect(keyReleaseEvent);
|
||||
debug("disconnecting updateVisualization");
|
||||
Script.update.disconnect(updateVisualization);
|
||||
});
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
"use strict";
|
||||
/* jslint vars: true, plusplus: true, forin: true*/
|
||||
/* globals Tablet, Script, AvatarList, Users, Entities, MyAvatar, Camera, Overlays, Vec3, Quat, Controller, print, getControllerWorldLocation */
|
||||
/*jslint vars:true, plusplus:true, forin:true*/
|
||||
/*global Tablet, Settings, Script, AvatarList, Users, Entities, MyAvatar, Camera, Overlays, Vec3, Quat, HMD, Controller, Account, UserActivityLogger, Messages, Window, XMLHttpRequest, print, location, getControllerWorldLocation, GlobalServices*/
|
||||
/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */
|
||||
//
|
||||
// pal.js
|
||||
|
@ -14,6 +14,8 @@
|
|||
|
||||
(function() { // BEGIN LOCAL_SCOPE
|
||||
|
||||
var populateNearbyUserList, color, textures, removeOverlays, controllerComputePickRay, onTabletButtonClicked, onTabletScreenChanged, receiveMessage, avatarDisconnected, clearLocalQMLDataAndClosePAL, createAudioInterval, tablet, CHANNEL, getConnectionData, findableByChanged; // forward references;
|
||||
|
||||
// hardcoding these as it appears we cannot traverse the originalTextures in overlays??? Maybe I've missed
|
||||
// something, will revisit as this is sorta horrible.
|
||||
var UNSELECTED_TEXTURES = {
|
||||
|
@ -97,9 +99,8 @@ ExtendedOverlay.prototype.hover = function (hovering) {
|
|||
if (this.key === lastHoveringId) {
|
||||
if (hovering) {
|
||||
return;
|
||||
} else {
|
||||
lastHoveringId = 0;
|
||||
}
|
||||
lastHoveringId = 0;
|
||||
}
|
||||
this.editOverlay({color: color(this.selected, hovering, this.audioLevel)});
|
||||
if (this.model) {
|
||||
|
@ -214,9 +215,8 @@ function convertDbToLinear(decibels) {
|
|||
// but, your perception is that something 2x as loud is +10db
|
||||
// so we go from -60 to +20 or 1/64x to 4x. For now, we can
|
||||
// maybe scale the signal this way??
|
||||
return Math.pow(2, decibels/10.0);
|
||||
return Math.pow(2, decibels / 10.0);
|
||||
}
|
||||
|
||||
function fromQml(message) { // messages are {method, params}, like json-rpc. See also sendToQml.
|
||||
var data;
|
||||
switch (message.method) {
|
||||
|
@ -247,7 +247,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See
|
|||
});
|
||||
}
|
||||
break;
|
||||
case 'refresh':
|
||||
case 'refreshNearby':
|
||||
data = {};
|
||||
ExtendedOverlay.some(function (overlay) { // capture the audio data
|
||||
data[overlay.key] = overlay;
|
||||
|
@ -257,8 +257,12 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See
|
|||
if (message.params.filter !== undefined) {
|
||||
Settings.setValue('pal/filtered', !!message.params.filter);
|
||||
}
|
||||
populateUserList(message.params.selected, data);
|
||||
UserActivityLogger.palAction("refresh", "");
|
||||
populateNearbyUserList(message.params.selected, data);
|
||||
UserActivityLogger.palAction("refresh_nearby", "");
|
||||
break;
|
||||
case 'refreshConnections':
|
||||
getConnectionData();
|
||||
UserActivityLogger.palAction("refresh_connections", "");
|
||||
break;
|
||||
case 'displayNameUpdate':
|
||||
if (MyAvatar.displayName !== message.params) {
|
||||
|
@ -266,6 +270,18 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See
|
|||
UserActivityLogger.palAction("display_name_change", "");
|
||||
}
|
||||
break;
|
||||
case 'goToUser':
|
||||
location.goToUser(message.params);
|
||||
UserActivityLogger.palAction("go_to_user", "");
|
||||
break;
|
||||
case 'setAvailability':
|
||||
GlobalServices.findableBy = message.params;
|
||||
UserActivityLogger.palAction("set_availability", "");
|
||||
print('Setting availability:', JSON.stringify(message));
|
||||
break;
|
||||
case 'getAvailability':
|
||||
findableByChanged(GlobalServices.findableBy);
|
||||
break;
|
||||
default:
|
||||
print('Unrecognized message from Pal.qml:', JSON.stringify(message));
|
||||
}
|
||||
|
@ -274,6 +290,147 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See
|
|||
function sendToQml(message) {
|
||||
tablet.sendToQml(message);
|
||||
}
|
||||
function updateUser(data) {
|
||||
print('PAL update:', JSON.stringify(data));
|
||||
sendToQml({ method: 'updateUsername', params: data });
|
||||
}
|
||||
//
|
||||
// User management services
|
||||
//
|
||||
// These are prototype versions that will be changed when the back end changes.
|
||||
var METAVERSE_BASE = 'https://metaverse.highfidelity.com';
|
||||
|
||||
function request(url, callback) { // cb(error, responseOfCorrectContentType) of url. General for 'get' text/html/json, but without redirects.
|
||||
var httpRequest = new XMLHttpRequest();
|
||||
// QT bug: apparently doesn't handle onload. Workaround using readyState.
|
||||
httpRequest.onreadystatechange = function () {
|
||||
var READY_STATE_DONE = 4;
|
||||
var HTTP_OK = 200;
|
||||
if (httpRequest.readyState >= READY_STATE_DONE) {
|
||||
var error = (httpRequest.status !== HTTP_OK) && httpRequest.status.toString() + ':' + httpRequest.statusText,
|
||||
response = !error && httpRequest.responseText,
|
||||
contentType = !error && httpRequest.getResponseHeader('content-type');
|
||||
if (!error && contentType.indexOf('application/json') === 0) {
|
||||
try {
|
||||
response = JSON.parse(response);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
}
|
||||
callback(error, response);
|
||||
}
|
||||
};
|
||||
httpRequest.open("GET", url, true);
|
||||
httpRequest.send();
|
||||
}
|
||||
function requestJSON(url, callback) { // callback(data) if successfull. Logs otherwise.
|
||||
request(url, function (error, response) {
|
||||
if (error || (response.status !== 'success')) {
|
||||
print("Error: unable to get", url, error || response.status);
|
||||
return;
|
||||
}
|
||||
callback(response.data);
|
||||
});
|
||||
}
|
||||
function getProfilePicture(username, callback) { // callback(url) if successfull. (Logs otherwise)
|
||||
// FIXME Prototype scrapes profile picture. We should include in user status, and also make available somewhere for myself
|
||||
request(METAVERSE_BASE + '/users/' + username, function (error, html) {
|
||||
var matched = !error && html.match(/img class="users-img" src="([^"]*)"/);
|
||||
if (!matched) {
|
||||
print('Error: Unable to get profile picture for', username, error);
|
||||
return;
|
||||
}
|
||||
callback(matched[1]);
|
||||
});
|
||||
}
|
||||
function getAvailableConnections(domain, callback) { // callback([{usename, location}...]) if successfull. (Logs otherwise)
|
||||
// The back end doesn't do user connections yet. Fake it by getting all users that have made themselves accessible to us,
|
||||
// and pretending that they are all connections.
|
||||
function getData(cb) {
|
||||
requestJSON(METAVERSE_BASE + '/api/v1/users?status=online', function (connectionsData) {
|
||||
|
||||
// The above does not include friend status. Fetch that separately.
|
||||
requestJSON(METAVERSE_BASE + '/api/v1/user/friends', function (friendsData) {
|
||||
var users = connectionsData.users || [], friends = friendsData.friends || [];
|
||||
users.forEach(function (user) {
|
||||
user.connection = (friends.indexOf(user.username) < 0) ? 'connection' : 'friend';
|
||||
});
|
||||
|
||||
// The back end doesn't include the profile picture data, but we can add that here.
|
||||
// For our current purposes, there's no need to be fancy and try to reduce latency by doing some number of requests in parallel,
|
||||
// so these requests are all sequential.
|
||||
function addPicture(index) {
|
||||
if (index >= users.length) {
|
||||
return cb(users);
|
||||
}
|
||||
var user = users[index];
|
||||
getProfilePicture(user.username, function (url) {
|
||||
user.profileUrl = url;
|
||||
addPicture(index + 1);
|
||||
});
|
||||
}
|
||||
addPicture(0);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (domain) {
|
||||
// The back end doesn't keep sessionUUID in the location data yet. Fake it by finding the avatar closest to the path.
|
||||
var positions = {};
|
||||
AvatarList.getAvatarIdentifiers().forEach(function (id) {
|
||||
positions[id || ''] = AvatarList.getAvatar(id).position; // Don't use null id as a key. Properties must be a string, and we don't want 'null'.
|
||||
});
|
||||
getData(function (users) {
|
||||
// The endpoint in getData doesn't take a domain filter. So filter out the unwanted stuff now.
|
||||
domain = domain.slice(1, -1); // without curly braces
|
||||
users = users.filter(function (user) { return (user.location.domain || (user.location.root && user.location.root.domain) || {}).id === domain; });
|
||||
|
||||
// Now fill in the sessionUUID as if it were in the data all along.
|
||||
users.forEach(function (user) {
|
||||
var coordinates = user.location.path.match(/\/([^,]+)\,([^,]+),([^\/]+)\//);
|
||||
if (coordinates) {
|
||||
var position = {x: Number(coordinates[1]), y: Number(coordinates[2]), z: Number(coordinates[3])};
|
||||
var none = 'not found', closestId = none, bestDistance = Infinity, distance, id;
|
||||
for (id in positions) {
|
||||
distance = Vec3.distance(position, positions[id]);
|
||||
if (distance < bestDistance) {
|
||||
closestId = id;
|
||||
bestDistance = distance;
|
||||
}
|
||||
}
|
||||
if (closestId !== none) {
|
||||
user.location.sessionUUID = closestId;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
callback(users);
|
||||
});
|
||||
} else { // We don't need to filter, nor add any sessionUUID data
|
||||
getData(callback);
|
||||
}
|
||||
}
|
||||
|
||||
function getConnectionData(domain) { // Update all the usernames that I am entitled to see, using my login but not dependent on canKick.
|
||||
function frob(user) { // get into the right format
|
||||
return {
|
||||
sessionId: user.location.sessionUUID || '',
|
||||
userName: user.username,
|
||||
connection: user.connection,
|
||||
profileUrl: user.profileUrl,
|
||||
placeName: (user.location.root || user.location.domain || {}).name || ''
|
||||
};
|
||||
}
|
||||
getAvailableConnections(domain, function (users) {
|
||||
if (domain) {
|
||||
users.forEach(function (user) {
|
||||
updateUser(frob(user));
|
||||
});
|
||||
} else {
|
||||
sendToQml({ method: 'connections', params: users.map(frob) });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
// Main operations.
|
||||
|
@ -285,15 +442,16 @@ function addAvatarNode(id) {
|
|||
solid: true,
|
||||
alpha: 0.8,
|
||||
color: color(selected, false, 0.0),
|
||||
ignoreRayIntersection: false}, selected, !conserveResources);
|
||||
ignoreRayIntersection: false
|
||||
}, selected, !conserveResources);
|
||||
}
|
||||
// Each open/refresh will capture a stable set of avatarsOfInterest, within the specified filter.
|
||||
var avatarsOfInterest = {};
|
||||
function populateUserList(selectData, oldAudioData) {
|
||||
var filter = Settings.getValue('pal/filtered') && {distance: Settings.getValue('pal/nearDistance')};
|
||||
var data = [], avatars = AvatarList.getAvatarIdentifiers();
|
||||
avatarsOfInterest = {};
|
||||
var myPosition = filter && Camera.position,
|
||||
function populateNearbyUserList(selectData, oldAudioData) {
|
||||
var filter = Settings.getValue('pal/filtered') && {distance: Settings.getValue('pal/nearDistance')},
|
||||
data = [],
|
||||
avatars = AvatarList.getAvatarIdentifiers(),
|
||||
myPosition = filter && Camera.position,
|
||||
frustum = filter && Camera.frustum,
|
||||
verticalHalfAngle = filter && (frustum.fieldOfView / 2),
|
||||
horizontalHalfAngle = filter && (verticalHalfAngle * frustum.aspectRatio),
|
||||
|
@ -301,7 +459,8 @@ function populateUserList(selectData, oldAudioData) {
|
|||
front = filter && Quat.getFront(orientation),
|
||||
verticalAngleNormal = filter && Quat.getRight(orientation),
|
||||
horizontalAngleNormal = filter && Quat.getUp(orientation);
|
||||
avatars.forEach(function (id) { // sorting the identifiers is just an aid for debugging
|
||||
avatarsOfInterest = {};
|
||||
avatars.forEach(function (id) {
|
||||
var avatar = AvatarList.getAvatar(id);
|
||||
var name = avatar.sessionDisplayName;
|
||||
if (!name) {
|
||||
|
@ -323,8 +482,10 @@ function populateUserList(selectData, oldAudioData) {
|
|||
}
|
||||
var oldAudio = oldAudioData && oldAudioData[id];
|
||||
var avatarPalDatum = {
|
||||
profileUrl: '',
|
||||
displayName: name,
|
||||
userName: '',
|
||||
connection: '',
|
||||
sessionId: id || '',
|
||||
audioLevel: (oldAudio && oldAudio.audioLevel) || 0.0,
|
||||
avgAudioLevel: (oldAudio && oldAudio.avgAudioLevel) || 0.0,
|
||||
|
@ -337,12 +498,19 @@ function populateUserList(selectData, oldAudioData) {
|
|||
// Everyone needs to see admin status. Username and fingerprint returns default constructor output if the requesting user isn't an admin.
|
||||
Users.requestUsernameFromID(id);
|
||||
avatarsOfInterest[id] = true;
|
||||
} else {
|
||||
// Return our username from the Account API
|
||||
avatarPalDatum.userName = Account.username;
|
||||
getProfilePicture(avatarPalDatum.userName, function (url) {
|
||||
sendToQml({ method: 'updateUsername', params: { profileUrl: url } });
|
||||
});
|
||||
}
|
||||
data.push(avatarPalDatum);
|
||||
print('PAL data:', JSON.stringify(avatarPalDatum));
|
||||
});
|
||||
getConnectionData(location.domainId); // Even admins don't get relationship data in requestUsernameFromID (which is still needed for admin status, which comes from domain).
|
||||
conserveResources = Object.keys(avatarsOfInterest).length > 20;
|
||||
sendToQml({ method: 'users', params: data });
|
||||
sendToQml({ method: 'nearbyUsers', params: data });
|
||||
if (selectData) {
|
||||
selectData[2] = true;
|
||||
sendToQml({ method: 'select', params: selectData });
|
||||
|
@ -351,15 +519,15 @@ function populateUserList(selectData, oldAudioData) {
|
|||
|
||||
// The function that handles the reply from the server
|
||||
function usernameFromIDReply(id, username, machineFingerprint, isAdmin) {
|
||||
var data = [
|
||||
(MyAvatar.sessionUUID === id) ? '' : id, // Pal.qml recognizes empty id specially.
|
||||
var data = {
|
||||
sessionId: (MyAvatar.sessionUUID === id) ? '' : id, // Pal.qml recognizes empty id specially.
|
||||
// If we get username (e.g., if in future we receive it when we're friends), use it.
|
||||
// Otherwise, use valid machineFingerprint (which is not valid when not an admin).
|
||||
username || (Users.canKick && machineFingerprint) || '',
|
||||
isAdmin
|
||||
];
|
||||
userName: username || (Users.canKick && machineFingerprint) || '',
|
||||
admin: isAdmin
|
||||
};
|
||||
// Ship the data off to QML
|
||||
sendToQml({ method: 'updateUsername', params: data });
|
||||
updateUser(data);
|
||||
}
|
||||
|
||||
var pingPong = true;
|
||||
|
@ -381,16 +549,12 @@ function updateOverlays() {
|
|||
var target = avatar.position;
|
||||
var distance = Vec3.distance(target, eye);
|
||||
var offset = 0.2;
|
||||
|
||||
// base offset on 1/2 distance from hips to head if we can
|
||||
var headIndex = avatar.getJointIndex("Head");
|
||||
var diff = Vec3.subtract(target, eye); // get diff between target and eye (a vector pointing to the eye from avatar position)
|
||||
var headIndex = avatar.getJointIndex("Head"); // base offset on 1/2 distance from hips to head if we can
|
||||
if (headIndex > 0) {
|
||||
offset = avatar.getAbsoluteJointTranslationInObjectFrame(headIndex).y / 2;
|
||||
}
|
||||
|
||||
// get diff between target and eye (a vector pointing to the eye from avatar position)
|
||||
var diff = Vec3.subtract(target, eye);
|
||||
|
||||
// move a bit in front, towards the camera
|
||||
target = Vec3.subtract(target, Vec3.multiply(Vec3.normalize(diff), offset));
|
||||
|
||||
|
@ -418,7 +582,7 @@ function updateOverlays() {
|
|||
overlay.deleteOverlay();
|
||||
}
|
||||
});
|
||||
// We could re-populateUserList if anything added or removed, but not for now.
|
||||
// We could re-populateNearbyUserList if anything added or removed, but not for now.
|
||||
HighlightedEntity.updateOverlays();
|
||||
}
|
||||
function removeOverlays() {
|
||||
|
@ -543,6 +707,7 @@ function startup() {
|
|||
Messages.subscribe(CHANNEL);
|
||||
Messages.messageReceived.connect(receiveMessage);
|
||||
Users.avatarDisconnected.connect(avatarDisconnected);
|
||||
GlobalServices.findableByChanged.connect(findableByChanged);
|
||||
}
|
||||
|
||||
startup();
|
||||
|
@ -579,7 +744,8 @@ function onTabletButtonClicked() {
|
|||
tablet.loadQMLSource("../Pal.qml");
|
||||
onPalScreen = true;
|
||||
Users.requestsDomainListData = true;
|
||||
populateUserList();
|
||||
populateNearbyUserList();
|
||||
findableByChanged(GlobalServices.findableBy);
|
||||
isWired = true;
|
||||
Script.update.connect(updateOverlays);
|
||||
Controller.mousePressEvent.connect(handleMouseEvent);
|
||||
|
@ -607,8 +773,7 @@ function onTabletScreenChanged(type, url) {
|
|||
//
|
||||
var CHANNEL = 'com.highfidelity.pal';
|
||||
function receiveMessage(channel, messageString, senderID) {
|
||||
if ((channel !== CHANNEL) ||
|
||||
(senderID !== MyAvatar.sessionUUID)) {
|
||||
if ((channel !== CHANNEL) || (senderID !== MyAvatar.sessionUUID)) {
|
||||
return;
|
||||
}
|
||||
var message = JSON.parse(messageString);
|
||||
|
@ -633,7 +798,7 @@ function scaleAudio(val) {
|
|||
if (val <= LOUDNESS_FLOOR) {
|
||||
audioLevel = val / LOUDNESS_FLOOR * LOUDNESS_SCALE;
|
||||
} else {
|
||||
audioLevel = (val -(LOUDNESS_FLOOR -1 )) * LOUDNESS_SCALE;
|
||||
audioLevel = (val - (LOUDNESS_FLOOR - 1)) * LOUDNESS_SCALE;
|
||||
}
|
||||
if (audioLevel > 1.0) {
|
||||
audioLevel = 1;
|
||||
|
@ -659,14 +824,14 @@ function getAudioLevel(id) {
|
|||
audioLevel = scaleAudio(Math.log(data.accumulatedLevel + 1) / LOG2);
|
||||
|
||||
// decay avgAudioLevel
|
||||
avgAudioLevel = Math.max((1-AUDIO_PEAK_DECAY) * (data.avgAudioLevel || 0), audioLevel);
|
||||
avgAudioLevel = Math.max((1 - AUDIO_PEAK_DECAY) * (data.avgAudioLevel || 0), audioLevel);
|
||||
|
||||
data.avgAudioLevel = avgAudioLevel;
|
||||
data.audioLevel = audioLevel;
|
||||
|
||||
// now scale for the gain. Also, asked to boost the low end, so one simple way is
|
||||
// to take sqrt of the value. Lets try that, see how it feels.
|
||||
avgAudioLevel = Math.min(1.0, Math.sqrt(avgAudioLevel *(sessionGains[id] || 0.75)));
|
||||
avgAudioLevel = Math.min(1.0, Math.sqrt(avgAudioLevel * (sessionGains[id] || 0.75)));
|
||||
}
|
||||
return [audioLevel, avgAudioLevel];
|
||||
}
|
||||
|
@ -677,9 +842,8 @@ function createAudioInterval(interval) {
|
|||
return Script.setInterval(function () {
|
||||
var param = {};
|
||||
AvatarList.getAvatarIdentifiers().forEach(function (id) {
|
||||
var level = getAudioLevel(id);
|
||||
// qml didn't like an object with null/empty string for a key, so...
|
||||
var userId = id || 0;
|
||||
var level = getAudioLevel(id),
|
||||
userId = id || 0; // qml didn't like an object with null/empty string for a key, so...
|
||||
param[userId] = level;
|
||||
});
|
||||
sendToQml({method: 'updateAudioLevel', params: param});
|
||||
|
@ -691,6 +855,20 @@ function avatarDisconnected(nodeID) {
|
|||
sendToQml({method: 'avatarDisconnected', params: [nodeID]});
|
||||
}
|
||||
|
||||
function findableByChanged(usernameAvailability) {
|
||||
// Update PAL availability dropdown
|
||||
// Default to "friends" if undeterminable
|
||||
var availability = 1;
|
||||
if (usernameAvailability === "all") {
|
||||
availability = 0;
|
||||
} else if (usernameAvailability === "friends") {
|
||||
availability = 1;
|
||||
} else if (usernameAvailability === "none") {
|
||||
availability = 2;
|
||||
}
|
||||
sendToQml({ method: 'updateAvailability', params: availability });
|
||||
}
|
||||
|
||||
function clearLocalQMLDataAndClosePAL() {
|
||||
sendToQml({ method: 'clearLocalQMLData' });
|
||||
}
|
||||
|
@ -708,6 +886,7 @@ function shutdown() {
|
|||
Messages.subscribe(CHANNEL);
|
||||
Messages.messageReceived.disconnect(receiveMessage);
|
||||
Users.avatarDisconnected.disconnect(avatarDisconnected);
|
||||
GlobalServices.findableByChanged.disconnect(findableByChanged);
|
||||
off();
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue