diff --git a/dist/license.md b/dist/license.md index f7f74ab5aa..3300388a79 100644 --- a/dist/license.md +++ b/dist/license.md @@ -4,6 +4,7 @@ Icon Name | License | Origin/Author --- | --- | --- qt_themes/default/icons/16x16/checked.png | Free for non-commercial use qt_themes/default/icons/16x16/connected.png | CC BY-ND 3.0 | https://icons8.com +qt_themes/default/icons/16x16/connected_notification.png | CC BY-ND 3.0 | https://icons8.com qt_themes/default/icons/16x16/disconnected.png | CC BY-ND 3.0 | https://icons8.com qt_themes/default/icons/16x16/failed.png | Free for non-commercial use qt_themes/default/icons/16x16/lock.png | CC BY-ND 3.0 | https://icons8.com @@ -16,6 +17,7 @@ qt_themes/default/icons/48x48/plus.png | CC0 1.0 | Designed by BreadFish64 from qt_themes/default/icons/48x48/sd_card.png | CC BY-ND 3.0 | https://icons8.com qt_themes/qdarkstyle/icons/16x16/checked.png | Free for non-commercial use qt_themes/qdarkstyle/icons/16x16/connected.png | CC BY-ND 3.0 | https://icons8.com +qt_themes/qdarkstyle/icons/16x16/connected_notification.png | CC BY-ND 3.0 | https://icons8.com qt_themes/qdarkstyle/icons/16x16/disconnected.png | CC BY-ND 3.0 | https://icons8.com qt_themes/qdarkstyle/icons/16x16/failed.png | Free for non-commercial use qt_themes/qdarkstyle/icons/16x16/lock.png | CC BY-ND 3.0 | https://icons8.com @@ -27,6 +29,7 @@ qt_themes/qdarkstyle/icons/48x48/no_avatar.png | CC BY-ND 3.0 | https://icons8.c qt_themes/qdarkstyle/icons/48x48/plus.png | CC0 1.0 | Designed by BreadFish64 from the Citra team qt_themes/qdarkstyle/icons/48x48/sd_card.png | CC BY-ND 3.0 | https://icons8.com qt_themes/colorful/icons/16x16/connected.png | CC BY-ND 3.0 | https://icons8.com +qt_themes/colorful/icons/16x16/connected_notification.png | CC BY-ND 3.0 | https://icons8.com qt_themes/colorful/icons/16x16/disconnected.png | CC BY-ND 3.0 | https://icons8.com qt_themes/colorful/icons/16x16/lock.png | CC BY-ND 3.0 | https://icons8.com qt_themes/colorful/icons/256x256/plus_folder.png | CC BY-ND 3.0 | https://icons8.com diff --git a/dist/qt_themes/colorful/icons/16x16/connected_notification.png b/dist/qt_themes/colorful/icons/16x16/connected_notification.png new file mode 100644 index 0000000000..0dfe032d58 Binary files /dev/null and b/dist/qt_themes/colorful/icons/16x16/connected_notification.png differ diff --git a/dist/qt_themes/colorful/style.qrc b/dist/qt_themes/colorful/style.qrc index dcf8b06e88..b8d47460fa 100644 --- a/dist/qt_themes/colorful/style.qrc +++ b/dist/qt_themes/colorful/style.qrc @@ -2,6 +2,7 @@ icons/index.theme icons/16x16/connected.png + icons/16x16/connected_notification.png icons/16x16/disconnected.png icons/16x16/lock.png icons/48x48/bad_folder.png diff --git a/dist/qt_themes/colorful_dark/style.qrc b/dist/qt_themes/colorful_dark/style.qrc index 00a7598fef..9c531fe1ba 100644 --- a/dist/qt_themes/colorful_dark/style.qrc +++ b/dist/qt_themes/colorful_dark/style.qrc @@ -2,6 +2,7 @@ icons/index.theme ../colorful/icons/16x16/connected.png + ../colorful/icons/16x16/connected_notification.png ../colorful/icons/16x16/disconnected.png icons/16x16/lock.png ../colorful/icons/48x48/bad_folder.png diff --git a/dist/qt_themes/default/default.qrc b/dist/qt_themes/default/default.qrc index 4840532a2e..cf011680f2 100644 --- a/dist/qt_themes/default/default.qrc +++ b/dist/qt_themes/default/default.qrc @@ -10,6 +10,8 @@ icons/16x16/disconnected.png + icons/16x16/connected_notification.png + icons/16x16/lock.png icons/48x48/bad_folder.png diff --git a/dist/qt_themes/default/icons/16x16/connected_notification.png b/dist/qt_themes/default/icons/16x16/connected_notification.png new file mode 100644 index 0000000000..e64901378b Binary files /dev/null and b/dist/qt_themes/default/icons/16x16/connected_notification.png differ diff --git a/dist/qt_themes/qdarkstyle/icons/16x16/connected_notification.png b/dist/qt_themes/qdarkstyle/icons/16x16/connected_notification.png new file mode 100644 index 0000000000..7cd8b9d293 Binary files /dev/null and b/dist/qt_themes/qdarkstyle/icons/16x16/connected_notification.png differ diff --git a/dist/qt_themes/qdarkstyle/style.qrc b/dist/qt_themes/qdarkstyle/style.qrc index c151238e16..0a4424b7fe 100644 --- a/dist/qt_themes/qdarkstyle/style.qrc +++ b/dist/qt_themes/qdarkstyle/style.qrc @@ -3,6 +3,7 @@ icons/index.theme icons/16x16/connected.png icons/16x16/disconnected.png + icons/16x16/connected_notification.png icons/16x16/lock.png icons/48x48/bad_folder.png icons/48x48/chip.png diff --git a/license.txt b/license.txt index fe47594db5..93078088c9 100644 --- a/license.txt +++ b/license.txt @@ -345,6 +345,7 @@ Icon Name | License | Origin/Author --- | --- | --- checked.png | Free for non-commercial use connected.png | CC BY-ND 3.0 | https://icons8.com +connected_notification.png | CC BY-ND 3.0 | https://icons8.com disconnected.png | CC BY-ND 3.0 | https://icons8.com failed.png | Free for non-commercial use lock.png | CC BY-ND 3.0 | https://icons8.com diff --git a/src/citra_qt/multiplayer/chat_room.cpp b/src/citra_qt/multiplayer/chat_room.cpp index 94dc6c3570..4c4150d6a5 100644 --- a/src/citra_qt/multiplayer/chat_room.cpp +++ b/src/citra_qt/multiplayer/chat_room.cpp @@ -34,6 +34,24 @@ public: nickname = QString::fromStdString(chat.nickname); username = QString::fromStdString(chat.username); message = QString::fromStdString(chat.message); + + // Check for user pings + QString cur_nickname, cur_username; + if (auto room = Network::GetRoomMember().lock()) { + cur_nickname = QString::fromStdString(room->GetNickname()); + cur_username = QString::fromStdString(room->GetUsername()); + } + if (message.contains(QString("@").append(cur_nickname)) || + (!cur_username.isEmpty() && message.contains(QString("@").append(cur_username)))) { + + contains_ping = true; + } else { + contains_ping = false; + } + } + + bool ContainsPing() const { + return contains_ping; } /// Format the message using the players color @@ -45,19 +63,28 @@ public: } else { name = QString("%1 (%2)").arg(nickname, username); } - return QString("[%1] <%3> %4") - .arg(timestamp, color, name.toHtmlEscaped(), message.toHtmlEscaped()); + + QString style; + if (ContainsPing()) { + // Add a background color to these messages + style = QString("background-color: %1").arg(ping_color); + } + + return QString("[%1] <%3> %5") + .arg(timestamp, color, name.toHtmlEscaped(), style, message.toHtmlEscaped()); } private: static constexpr std::array player_color = { {"#0000FF", "#FF0000", "#8A2BE2", "#FF69B4", "#1E90FF", "#008000", "#00FF7F", "#B22222", "#DAA520", "#FF4500", "#2E8B57", "#5F9EA0", "#D2691E", "#9ACD32", "#FF7F50", "FFFF00"}}; + static constexpr char ping_color[] = "#FFFF00"; QString timestamp; QString nickname; QString username; QString message; + bool contains_ping; }; class StatusMessage { @@ -240,6 +267,9 @@ void ChatRoom::OnChatReceive(const Network::ChatEntry& chat) { } auto player = std::distance(members.begin(), it); ChatMessage m(chat); + if (m.ContainsPing()) { + emit UserPinged(); + } AppendChatMessage(m.GetPlayerChatMessage(player)); } } diff --git a/src/citra_qt/multiplayer/chat_room.h b/src/citra_qt/multiplayer/chat_room.h index 58cb25e832..70f786dbec 100644 --- a/src/citra_qt/multiplayer/chat_room.h +++ b/src/citra_qt/multiplayer/chat_room.h @@ -51,6 +51,7 @@ public slots: signals: void ChatReceived(const Network::ChatEntry&); void StatusMessageReceived(const Network::StatusMessageEntry&); + void UserPinged(); private: static constexpr u32 max_chat_lines = 1000; diff --git a/src/citra_qt/multiplayer/client_room.cpp b/src/citra_qt/multiplayer/client_room.cpp index 84a425189e..d87a3e6e16 100644 --- a/src/citra_qt/multiplayer/client_room.cpp +++ b/src/citra_qt/multiplayer/client_room.cpp @@ -49,6 +49,7 @@ ClientRoomWindow::ClientRoomWindow(QWidget* parent) }); ui->moderation->setDefault(false); ui->moderation->setAutoDefault(false); + connect(ui->chat, &ChatRoom::UserPinged, this, &ClientRoomWindow::ShowNotification); UpdateView(); } diff --git a/src/citra_qt/multiplayer/client_room.h b/src/citra_qt/multiplayer/client_room.h index 7d4f2b238d..c40d324c34 100644 --- a/src/citra_qt/multiplayer/client_room.h +++ b/src/citra_qt/multiplayer/client_room.h @@ -27,6 +27,7 @@ public slots: signals: void RoomInformationChanged(const Network::RoomInformation&); void StateChanged(const Network::RoomMember::State&); + void ShowNotification(); private: void Disconnect(); diff --git a/src/citra_qt/multiplayer/state.cpp b/src/citra_qt/multiplayer/state.cpp index 2970c06918..b14e79f9bd 100644 --- a/src/citra_qt/multiplayer/state.cpp +++ b/src/citra_qt/multiplayer/state.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include +#include #include #include #include @@ -49,6 +50,13 @@ MultiplayerState::MultiplayerState(QWidget* parent, QStandardItemModel* game_lis connect(status_text, &ClickableLabel::clicked, this, &MultiplayerState::OnOpenNetworkRoom); connect(status_icon, &ClickableLabel::clicked, this, &MultiplayerState::OnOpenNetworkRoom); + + connect(static_cast(QApplication::instance()), &QApplication::focusChanged, this, + [this](QWidget* /*old*/, QWidget* now) { + if (client_room && client_room->isAncestorOf(now)) { + HideNotification(); + } + }); } MultiplayerState::~MultiplayerState() { @@ -173,7 +181,9 @@ void MultiplayerState::OnAnnounceFailed(const Common::WebResult& result) { } void MultiplayerState::UpdateThemedIcons() { - if (current_state == Network::RoomMember::State::Joined) { + if (show_notification) { + status_icon->setPixmap(QIcon::fromTheme("connected_notification").pixmap(16)); + } else if (current_state == Network::RoomMember::State::Joined) { status_icon->setPixmap(QIcon::fromTheme("connected").pixmap(16)); } else { status_icon->setPixmap(QIcon::fromTheme("disconnected").pixmap(16)); @@ -225,11 +235,28 @@ bool MultiplayerState::OnCloseRoom() { return true; } +void MultiplayerState::ShowNotification() { + if (client_room && client_room->isAncestorOf(QApplication::focusWidget())) + return; // Do not show notification if the chat window currently has focus + show_notification = true; + QApplication::alert(nullptr); + status_icon->setPixmap(QIcon::fromTheme("connected_notification").pixmap(16)); + status_text->setText(tr("New Messages Received")); +} + +void MultiplayerState::HideNotification() { + show_notification = false; + status_icon->setPixmap(QIcon::fromTheme("connected").pixmap(16)); + status_text->setText(tr("Connected")); +} + void MultiplayerState::OnOpenNetworkRoom() { if (auto member = Network::GetRoomMember().lock()) { if (member->IsConnected()) { if (client_room == nullptr) { client_room = new ClientRoomWindow(this); + connect(client_room, &ClientRoomWindow::ShowNotification, this, + &MultiplayerState::ShowNotification); } const std::string host_username = member->GetRoomInformation().host_username; if (host_username.empty()) { diff --git a/src/citra_qt/multiplayer/state.h b/src/citra_qt/multiplayer/state.h index a49288e834..8061d18cfd 100644 --- a/src/citra_qt/multiplayer/state.h +++ b/src/citra_qt/multiplayer/state.h @@ -48,6 +48,8 @@ public slots: void OnDirectConnectToRoom(); void OnAnnounceFailed(const Common::WebResult&); void UpdateThemedIcons(); + void ShowNotification(); + void HideNotification(); signals: void NetworkStateChanged(const Network::RoomMember::State&); @@ -69,6 +71,8 @@ private: bool has_mod_perms = false; Network::RoomMember::CallbackHandle state_callback_handle; Network::RoomMember::CallbackHandle error_callback_handle; + + bool show_notification = false; }; Q_DECLARE_METATYPE(Common::WebResult);