diff --git a/interface/resources/qml/hifi/NameCard.qml b/interface/resources/qml/hifi/NameCard.qml index be4bafe9a6..0ef86dd342 100644 --- a/interface/resources/qml/hifi/NameCard.qml +++ b/interface/resources/qml/hifi/NameCard.qml @@ -41,6 +41,7 @@ Item { property bool isMyCard: false property bool selected: false property bool isAdmin: false + property bool isPresent: true property string imageMaskColor: pal.color; property string profilePicBorderColor: (connectionStatus == "connection" ? hifi.colors.indigoAccent : (connectionStatus == "friend" ? hifi.colors.greenHighlight : imageMaskColor)) @@ -236,7 +237,7 @@ Item { color: hifi.colors.darkGray; MouseArea { anchors.fill: parent - enabled: selected && pal.activeTab == "nearbyTab" && thisNameCard.userName !== ""; + enabled: selected && pal.activeTab == "nearbyTab" && thisNameCard.userName !== "" && isPresent; hoverEnabled: enabled onClicked: { goToUserInDomain(thisNameCard.uuid); @@ -296,7 +297,8 @@ Item { } MouseArea { anchors.fill: parent - hoverEnabled: true + enabled: isPresent + hoverEnabled: enabled onClicked: letterbox(hifi.glyphs.question, "Domain Admin", "This user is an admin on this domain. Admins can Silence and Ban other users at their discretion - so be extra nice!") @@ -329,7 +331,7 @@ Item { color: hifi.colors.greenShadow; MouseArea { anchors.fill: parent - enabled: selected && pal.activeTab == "nearbyTab" && thisNameCard.userName !== ""; + enabled: selected && pal.activeTab == "nearbyTab" && thisNameCard.userName !== "" && isPresent; hoverEnabled: enabled onClicked: { goToUserInDomain(thisNameCard.uuid); @@ -358,7 +360,7 @@ Item { // Style radius: 4 color: "#c5c5c5" - visible: isMyCard || (selected && pal.activeTab == "nearbyTab") + visible: (isMyCard || (selected && pal.activeTab == "nearbyTab")) && isPresent // Rectangle for the zero-gain point on the VU meter Rectangle { id: vuMeterZeroGain @@ -433,7 +435,7 @@ Item { anchors.verticalCenter: nameCardVUMeter.verticalCenter; anchors.left: nameCardVUMeter.left; // Properties - visible: !isMyCard && selected && pal.activeTab == "nearbyTab"; + visible: !isMyCard && selected && pal.activeTab == "nearbyTab" && isPresent; value: Users.getAvatarGain(uuid) minimumValue: -60.0 maximumValue: 20.0 diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index f941ff12b3..b4b041587b 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -36,7 +36,7 @@ Rectangle { property int locationColumnWidth: 170; property int nearbyNameCardWidth: nearbyTable.width - (iAmAdmin ? (actionButtonWidth * 4) : (actionButtonWidth * 2)) - 4 - hifi.dimensions.scrollbarBackgroundWidth; property int connectionsNameCardWidth: connectionsTable.width - locationColumnWidth - actionButtonWidth - 4 - hifi.dimensions.scrollbarBackgroundWidth; - property var myData: ({profileUrl: "", displayName: "", userName: "", audioLevel: 0.0, avgAudioLevel: 0.0, admin: true, placeName: "", connection: ""}); // valid dummy until set + property var myData: ({profileUrl: "", displayName: "", userName: "", audioLevel: 0.0, avgAudioLevel: 0.0, admin: true, placeName: "", connection: "", isPresent: true}); // valid dummy until set property var ignored: ({}); // Keep a local list of ignored avatars & their data. Necessary because HashMap is slow to respond after ignoring. property var nearbyUserModelData: []; // This simple list is essentially a mirror of the nearbyUserModel listModel without all the extra complexities. property var connectionsUserModelData: []; // This simple list is essentially a mirror of the connectionsUserModel listModel without all the extra complexities. @@ -132,6 +132,7 @@ Rectangle { audioLevel: myData.audioLevel; avgAudioLevel: myData.avgAudioLevel; isMyCard: true; + isPresent: true; // Size width: myCardWidth; height: parent.height; @@ -576,8 +577,9 @@ Rectangle { // This Rectangle refers to each Row in the nearbyTable. rowDelegate: Rectangle { // The only way I know to specify a row height. // Size - height: styleData.selected ? rowHeight + 15 : rowHeight; - color: rowColor(styleData.selected, styleData.alternate); + height: rowHeight + (styleData.selected && model.isPresent ? 15 : 0); + color: rowColor(styleData.selected, styleData.alternate, model ? model.isPresent : true); + opacity: model.isPresent ? 1.0 : 0.6; } // This Item refers to the contents of each Cell @@ -586,6 +588,7 @@ Rectangle { property bool isCheckBox: styleData.role === "personalMute" || styleData.role === "ignore"; property bool isButton: styleData.role === "mute" || styleData.role === "kick"; property bool isAvgAudio: styleData.role === "avgAudioLevel"; + opacity: model.isPresent ? 1.0 : 0.6; // This NameCard refers to the cell that contains an avatar's // DisplayName and UserName @@ -593,7 +596,7 @@ Rectangle { id: nameCard; // Properties profileUrl: (model && model.profileUrl) || ""; - imageMaskColor: rowColor(styleData.selected, styleData.row % 2); + imageMaskColor: rowColor(styleData.selected, styleData.row % 2, model.isPresent); displayName: styleData.value; userName: model ? model.userName : ""; connectionStatus: model ? model.connection : ""; @@ -603,6 +606,7 @@ Rectangle { uuid: model ? model.sessionId : ""; selected: styleData.selected; isAdmin: model && model.admin; + isPresent: model && model.isPresent; // Size width: nearbyNameCardWidth; height: parent.height; @@ -899,7 +903,7 @@ Rectangle { rowDelegate: Rectangle { // Size height: rowHeight; - color: rowColor(styleData.selected, styleData.alternate); + color: rowColor(styleData.selected, styleData.alternate, model ? model.isPresent : true); } // This Item refers to the contents of each Cell @@ -912,7 +916,7 @@ Rectangle { // Properties visible: styleData.role === "userName"; profileUrl: (model && model.profileUrl) || ""; - imageMaskColor: rowColor(styleData.selected, styleData.row % 2); + imageMaskColor: rowColor(styleData.selected, styleData.row % 2, model.isPresent); displayName: ""; userName: model ? model.userName : ""; connectionStatus : model ? model.connection : ""; @@ -1155,8 +1159,8 @@ Rectangle { } } - function rowColor(selected, alternate) { - return selected ? hifi.colors.orangeHighlight : alternate ? hifi.colors.tableRowLightEven : hifi.colors.tableRowLightOdd; + function rowColor(selected, alternate, isPresent) { + return isPresent ? (selected ? hifi.colors.orangeHighlight : alternate ? hifi.colors.tableRowLightEven : hifi.colors.tableRowLightOdd) : hifi.colors.gray; } function findNearbySessionIndex(sessionId, optionalData) { // no findIndex in .qml var data = optionalData || nearbyUserModelData, length = data.length; @@ -1211,7 +1215,7 @@ Rectangle { } else if (userIndex < 0) { // If we've already refreshed the PAL and the avatar still isn't present in the model... if (alreadyRefreshed === true) { - letterbox('', '', 'The last editor of this object is either you or not among this list of users.'); + letterbox('', '', 'The user you attempted to select is no longer available.'); } else { pal.sendToScript({method: 'refresh', params: {selected: message.params}}); } @@ -1283,6 +1287,14 @@ Rectangle { delete ignored[sessionID]; break; case 'palIsStale': + if (message.params) { + var sessionID = message.params[0]; + var userIndex = findNearbySessionIndex(sessionID); + if (userIndex != -1) { + nearbyUserModel.setProperty(userIndex, "isPresent", false); + nearbyUserModelData[userIndex].isPresent = false; + } + } reloadNearby.glyph = hifi.glyphs.alert; reloadNearby.color = 2; break; diff --git a/scripts/system/pal.js b/scripts/system/pal.js index f0067e6843..95e208d126 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -479,7 +479,8 @@ function populateNearbyUserList(selectData, oldAudioData) { avgAudioLevel: (oldAudio && oldAudio.avgAudioLevel) || 0.0, admin: false, personalMute: !!id && Users.getPersonalMuteStatus(id), // expects proper boolean, not null - ignore: !!id && Users.getIgnoreStatus(id) // ditto + ignore: !!id && Users.getIgnoreStatus(id), // ditto + isPresent: true }; if (id) { addAvatarNode(id); // No overlay for ourselves @@ -852,8 +853,8 @@ function avatarAdded() { sendToQml({ method: 'palIsStale' }); } -function avatarRemoved() { - sendToQml({ method: 'palIsStale' }); +function avatarRemoved(avatarID) { + sendToQml({ method: 'palIsStale', params: [avatarID] }); } function avatarSessionChanged() {