diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 11cbd73970..cd866cecb2 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -262,8 +262,12 @@ void AvatarMixer::broadcastAvatarData() { // setup a PacketList for the avatarPackets auto avatarPacketList = NLPacketList::create(PacketType::BulkAvatarData); - if (avatar.getSessionDisplayName().isEmpty() && // We haven't set it yet... - nodeData->getReceivedIdentity()) { // ... but we have processed identity (with possible displayName). + if (nodeData->getAvatarSessionDisplayNameMustChange()) { + const QString& existingBaseDisplayName = nodeData->getBaseDisplayName(); + if (--_sessionDisplayNames[existingBaseDisplayName].second <= 0) { + _sessionDisplayNames.remove(existingBaseDisplayName); + } + QString baseName = avatar.getDisplayName().trimmed(); const QRegularExpression curses{ "fuck|shit|damn|cock|cunt" }; // POC. We may eventually want something much more elaborate (subscription?). baseName = baseName.replace(curses, "*"); // Replace rather than remove, so that people have a clue that the person's a jerk. @@ -276,11 +280,14 @@ void AvatarMixer::broadcastAvatarData() { QPair& soFar = _sessionDisplayNames[baseName]; // Inserts and answers 0, 0 if not already present, which is what we want. int& highWater = soFar.first; nodeData->setBaseDisplayName(baseName); - avatar.setSessionDisplayName((highWater > 0) ? baseName + "_" + QString::number(highWater) : baseName); + QString sessionDisplayName = (highWater > 0) ? baseName + "_" + QString::number(highWater) : baseName; + avatar.setSessionDisplayName(sessionDisplayName); highWater++; soFar.second++; // refcount nodeData->flagIdentityChange(); - sendIdentityPacket(nodeData, node); // Tell new node about its sessionUUID. Others will find out below. + nodeData->setAvatarSessionDisplayNameMustChange(false); + sendIdentityPacket(nodeData, node); // Tell node whose name changed about its new session display name. Others will find out below. + qDebug() << "Giving session display name" << sessionDisplayName << "to node with ID" << node->getUUID(); } // this is an AGENT we have received head data from @@ -584,7 +591,7 @@ void AvatarMixer::handleAvatarIdentityPacket(QSharedPointer mes if (avatar.processAvatarIdentity(identity)) { QMutexLocker nodeDataLocker(&nodeData->getMutex()); nodeData->flagIdentityChange(); - nodeData->setReceivedIdentity(); + nodeData->setAvatarSessionDisplayNameMustChange(true); } } } diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index f18cfdde1b..38db2e74d2 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -53,8 +53,8 @@ public: HRCTime getIdentityChangeTimestamp() const { return _identityChangeTimestamp; } void flagIdentityChange() { _identityChangeTimestamp = p_high_resolution_clock::now(); } - bool getReceivedIdentity() const { return _gotIdentity; } - void setReceivedIdentity() { _gotIdentity = true; } + bool getAvatarSessionDisplayNameMustChange() const { return _avatarSessionDisplayNameMustChange; } + void setAvatarSessionDisplayNameMustChange(bool set = true) { _avatarSessionDisplayNameMustChange = set; } void setFullRateDistance(float fullRateDistance) { _fullRateDistance = fullRateDistance; } float getFullRateDistance() const { return _fullRateDistance; } @@ -112,7 +112,7 @@ private: std::unordered_set _hasReceivedFirstPacketsFrom; HRCTime _identityChangeTimestamp; - bool _gotIdentity { false }; + bool _avatarSessionDisplayNameMustChange{ false }; float _fullRateDistance = FLT_MAX; float _maxAvatarDistance = FLT_MAX; diff --git a/interface/resources/fonts/hifi-glyphs.ttf b/interface/resources/fonts/hifi-glyphs.ttf index 09aefffdfe..c139a196d0 100644 Binary files a/interface/resources/fonts/hifi-glyphs.ttf and b/interface/resources/fonts/hifi-glyphs.ttf differ diff --git a/interface/resources/qml/hifi/NameCard.qml b/interface/resources/qml/hifi/NameCard.qml index c6daf53b9a..2725ea1617 100644 --- a/interface/resources/qml/hifi/NameCard.qml +++ b/interface/resources/qml/hifi/NameCard.qml @@ -28,7 +28,7 @@ Item { property string uuid: "" property string displayName: "" property string userName: "" - property int displayTextHeight: 18 + property real displayNameTextPixelSize: 18 property int usernameTextHeight: 12 property real audioLevel: 0.0 property bool isMyCard: false @@ -55,18 +55,112 @@ Item { width: parent.width - /*avatarImage.width - parent.spacing - */parent.anchors.leftMargin - parent.anchors.rightMargin height: childrenRect.height anchors.verticalCenter: parent.verticalCenter - // DisplayName Text + + // DisplayName field for my card + Rectangle { + id: myDisplayName + visible: isMyCard + // Size + width: parent.width + 70 + height: 35 + // Anchors + anchors.top: parent.top + anchors.left: parent.left + anchors.leftMargin: -10 + // 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 + } + } + 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" + } + onDoubleClicked: { + myDisplayNameText.selectAll(); + myDisplayNameText.focus = 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 + Rectangle { + id: myDisplayNameSpacer + width: myDisplayName.width + // Anchors + anchors.top: myDisplayName.bottom + height: 5 + visible: isMyCard + opacity: 0 + } + // DisplayName Text for others' cards FiraSansSemiBold { id: displayNameText // Properties text: thisNameCard.displayName elide: Text.ElideRight + visible: !isMyCard // Size width: parent.width // Anchors anchors.top: parent.top // Text Size - size: thisNameCard.displayTextHeight + size: displayNameTextPixelSize // Text Positioning verticalAlignment: Text.AlignVCenter // Style @@ -83,7 +177,7 @@ Item { // Size width: parent.width // Anchors - anchors.top: displayNameText.bottom + anchors.top: isMyCard ? myDisplayNameSpacer.bottom : displayNameText.bottom // Text Size size: thisNameCard.usernameTextHeight // Text Positioning @@ -105,7 +199,7 @@ Item { Rectangle { id: nameCardVUMeter // Size - width: ((gainSlider.value - gainSlider.minimumValue)/(gainSlider.maximumValue - gainSlider.minimumValue)) * parent.width + width: isMyCard ? myDisplayName.width - 20 : ((gainSlider.value - gainSlider.minimumValue)/(gainSlider.maximumValue - gainSlider.minimumValue)) * parent.width height: 8 // Anchors anchors.top: spacer.bottom diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index a57e76f864..549598dd2e 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -35,6 +35,17 @@ Rectangle { // Keep a local list of per-avatar gainSliderValueDBs. Far faster than fetching this data from the server. // NOTE: if another script modifies the per-avatar gain, this value won't be accurate! property var gainSliderValueDB: ({}); + + // The letterbox used for popup messages + LetterboxMessage { + id: letterboxMessage + z: 999 // Force the popup on top of everything else + } + function letterbox(message) { + letterboxMessage.text = message + letterboxMessage.visible = true + letterboxMessage.popupRadius = 0 + } // This is the container for the PAL Rectangle { @@ -176,8 +187,6 @@ Rectangle { TableViewColumn { visible: iAmAdmin role: "kick" - // The hacky spaces used to center text over the button, since I don't know how to apply a margin - // to column header text. title: "BAN" width: actionButtonWidth movable: false @@ -337,11 +346,6 @@ Rectangle { visible: iAmAdmin color: hifi.colors.lightGrayText } - function letterbox(message) { - letterboxMessage.text = message; - letterboxMessage.visible = true - - } // This Rectangle refers to the [?] popup button next to "NAMES" Rectangle { color: hifi.colors.tableBackgroundLight @@ -402,9 +406,6 @@ Rectangle { onExited: adminHelpText.color = hifi.colors.redHighlight } } - LetterboxMessage { - id: letterboxMessage - } } function findSessionIndex(sessionId, optionalData) { // no findIndex in .qml diff --git a/interface/resources/qml/styles-uit/HifiConstants.qml b/interface/resources/qml/styles-uit/HifiConstants.qml index 7eca1aa725..18bdd89799 100644 --- a/interface/resources/qml/styles-uit/HifiConstants.qml +++ b/interface/resources/qml/styles-uit/HifiConstants.qml @@ -312,10 +312,11 @@ Item { readonly property string error: "=" readonly property string settings: "@" readonly property string trash: "{" - readonly property string objectGroup: "" + readonly property string objectGroup: "\ue000" readonly property string cm: "}" readonly property string msvg79: "~" readonly property string deg: "\\" readonly property string px: "|" + readonly property string editPencil: "\ue00d" } } diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index f8805d3fc4..1fb68fce14 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -1047,7 +1047,7 @@ bool AvatarData::processAvatarIdentity(const Identity& identity) { } if (identity.displayName != _displayName) { - setDisplayName(identity.displayName); + _displayName = identity.displayName; hasIdentityChanged = true; } maybeUpdateSessionDisplayNameFromTransport(identity.sessionDisplayName); @@ -1094,6 +1094,9 @@ void AvatarData::setSkeletonModelURL(const QUrl& skeletonModelURL) { void AvatarData::setDisplayName(const QString& displayName) { _displayName = displayName; + _sessionDisplayName = ""; + + sendIdentityPacket(); qCDebug(avatars) << "Changing display name for avatar to" << displayName; } diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index c012ed8f67..5cef6013d9 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -211,7 +211,8 @@ enum class AvatarMixerPacketVersion : PacketVersion { HandControllerJoints, HasKillAvatarReason, SessionDisplayName, - Unignore + Unignore, + ImmediateSessionDisplayNameUpdates }; enum class DomainConnectRequestVersion : PacketVersion { diff --git a/scripts/system/pal.js b/scripts/system/pal.js index fb0d2b310b..2430ce6e87 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -237,6 +237,11 @@ pal.fromQml.connect(function (message) { // messages are {method, params}, like data = message.params; Users.setAvatarGain(data['sessionId'], data['gain']); break; + case 'displayNameUpdate': + if (MyAvatar.displayName != message.params) { + MyAvatar.displayName = message.params; + } + break; default: print('Unrecognized message from Pal.qml:', JSON.stringify(message)); }