From 7638f87f7421e705979119ef93d309893673df0a Mon Sep 17 00:00:00 2001 From: Tobias Date: Fri, 16 Feb 2024 13:34:10 +0100 Subject: [PATCH] Port several small multiplayer PRs from yuzu (#7419) * yuzu: Use displayed port on direct connect * Color player counts in the multiplayer public lobby list - Full lobbies have their player count displayed in red. - Lobbies with one slot left have their player count displayed in orange. - Empty lobbies have their player count grayed out. * Add hotkeys for multiplayer actions Default shortcuts were chosen as to be intuitive (use the first letter of the action, or the second word's first letter) and work on all types of keyboards. The hotkeys can be used while playing a game too, as they are application-wide. * Persist filters in multiplayer public lobby list After connecting to a room, the chosen filter text, "Games I Own", "Hide Empty Rooms" and "Hide Full Rooms" values are persisted to configuration so they are preserved across restarts. This makes it easier to rejoin a room if you regularly play the same game, or after a crash. * citra_qt/lobby: Fix multiplayer player count color in dark theme Co-Authored-By: Kevnkkm <56404895+kevnkkm@users.noreply.github.com> * Address review comments --------- Co-authored-by: Narr the Reg Co-authored-by: Hugo Locurcio Co-authored-by: Kevnkkm <56404895+kevnkkm@users.noreply.github.com> --- src/citra_qt/configuration/config.cpp | 25 +++++++++++++++- src/citra_qt/configuration/config.h | 2 +- src/citra_qt/main.cpp | 7 +++++ src/citra_qt/multiplayer/direct_connect.cpp | 5 ++-- src/citra_qt/multiplayer/lobby.cpp | 12 +++++++- src/citra_qt/multiplayer/lobby_p.h | 33 ++++++++++++++++++--- src/citra_qt/uisettings.h | 5 ++++ 7 files changed, 79 insertions(+), 10 deletions(-) diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp index 389a3becf8..54c8c4f4b4 100644 --- a/src/citra_qt/configuration/config.cpp +++ b/src/citra_qt/configuration/config.cpp @@ -54,7 +54,7 @@ const std::array, Settings::NativeAnalog::NumAnalogs> Config: // This must be in alphabetical order according to action name as it must have the same order as // UISetting::values.shortcuts, which is alphabetically ordered. // clang-format off -const std::array Config::default_hotkeys {{ +const std::array Config::default_hotkeys {{ {QStringLiteral("Advance Frame"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::ApplicationShortcut}}, {QStringLiteral("Audio Mute/Unmute"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+M"), Qt::WindowShortcut}}, {QStringLiteral("Audio Volume Down"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::WindowShortcut}}, @@ -71,6 +71,11 @@ const std::array Config::default_hotkeys {{ {QStringLiteral("Load Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F2"), Qt::WidgetWithChildrenShortcut}}, {QStringLiteral("Load File"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+O"), Qt::WidgetWithChildrenShortcut}}, {QStringLiteral("Load from Newest Slot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+V"), Qt::WindowShortcut}}, + {QStringLiteral("Multiplayer Browse Public Game Lobby"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+B"), Qt::ApplicationShortcut}}, + {QStringLiteral("Multiplayer Create Room"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+N"), Qt::ApplicationShortcut}}, + {QStringLiteral("Multiplayer Direct Connect to Room"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Shift"), Qt::ApplicationShortcut}}, + {QStringLiteral("Multiplayer Leave Room"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+L"), Qt::ApplicationShortcut}}, + {QStringLiteral("Multiplayer Show Current Room"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+R"), Qt::ApplicationShortcut}}, {QStringLiteral("Remove Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F3"), Qt::ApplicationShortcut}}, {QStringLiteral("Restart Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F6"), Qt::WindowShortcut}}, {QStringLiteral("Rotate Screens Upright"), QStringLiteral("Main Window"), {QStringLiteral("F8"), Qt::WindowShortcut}}, @@ -557,6 +562,15 @@ void Config::ReadMultiplayerValues() { UISettings::values.game_id = ReadSetting(QStringLiteral("game_id"), 0).toULongLong(); UISettings::values.room_description = ReadSetting(QStringLiteral("room_description"), QString{}).toString(); + UISettings::values.multiplayer_filter_text = + ReadSetting(QStringLiteral("multiplayer_filter_text"), QString{}).toString(); + UISettings::values.multiplayer_filter_games_owned = + ReadSetting(QStringLiteral("multiplayer_filter_games_owned"), false).toBool(); + UISettings::values.multiplayer_filter_hide_empty = + ReadSetting(QStringLiteral("multiplayer_filter_hide_empty"), false).toBool(); + UISettings::values.multiplayer_filter_hide_full = + ReadSetting(QStringLiteral("multiplayer_filter_hide_full"), false).toBool(); + // Read ban list back int size = qt_config->beginReadArray(QStringLiteral("username_ban_list")); UISettings::values.ban_list.first.resize(size); @@ -1074,6 +1088,15 @@ void Config::SaveMultiplayerValues() { WriteSetting(QStringLiteral("game_id"), UISettings::values.game_id, 0); WriteSetting(QStringLiteral("room_description"), UISettings::values.room_description, QString{}); + WriteSetting(QStringLiteral("multiplayer_filter_text"), + UISettings::values.multiplayer_filter_text, QString{}); + WriteSetting(QStringLiteral("multiplayer_filter_games_owned"), + UISettings::values.multiplayer_filter_games_owned, false); + WriteSetting(QStringLiteral("multiplayer_filter_hide_empty"), + UISettings::values.multiplayer_filter_hide_empty, false); + WriteSetting(QStringLiteral("multiplayer_filter_hide_full"), + UISettings::values.multiplayer_filter_hide_full, false); + // Write ban list qt_config->beginWriteArray(QStringLiteral("username_ban_list")); for (std::size_t i = 0; i < UISettings::values.ban_list.first.size(); ++i) { diff --git a/src/citra_qt/configuration/config.h b/src/citra_qt/configuration/config.h index 27be93711a..521c6baf9d 100644 --- a/src/citra_qt/configuration/config.h +++ b/src/citra_qt/configuration/config.h @@ -26,7 +26,7 @@ public: static const std::array default_buttons; static const std::array, Settings::NativeAnalog::NumAnalogs> default_analogs; - static const std::array default_hotkeys; + static const std::array default_hotkeys; private: void Initialize(const std::string& config_name); diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index 807da30e3c..4f179a35c4 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -647,6 +647,13 @@ void GMainWindow::InitializeHotkeys() { link_action_shortcut(ui->action_Advance_Frame, QStringLiteral("Advance Frame")); link_action_shortcut(ui->action_Load_from_Newest_Slot, QStringLiteral("Load from Newest Slot")); link_action_shortcut(ui->action_Save_to_Oldest_Slot, QStringLiteral("Save to Oldest Slot")); + link_action_shortcut(ui->action_View_Lobby, + QStringLiteral("Multiplayer Browse Public Game Lobby")); + link_action_shortcut(ui->action_Start_Room, QStringLiteral("Multiplayer Create Room")); + link_action_shortcut(ui->action_Connect_To_Room, + QStringLiteral("Multiplayer Direct Connect to Room")); + link_action_shortcut(ui->action_Show_Room, QStringLiteral("Multiplayer Show Current Room")); + link_action_shortcut(ui->action_Leave_Room, QStringLiteral("Multiplayer Leave Room")); const auto add_secondary_window_hotkey = [this](QKeySequence hotkey, const char* slot) { // This action will fire specifically when secondary_window is in focus diff --git a/src/citra_qt/multiplayer/direct_connect.cpp b/src/citra_qt/multiplayer/direct_connect.cpp index 936fd0435c..bdc866a753 100644 --- a/src/citra_qt/multiplayer/direct_connect.cpp +++ b/src/citra_qt/multiplayer/direct_connect.cpp @@ -80,9 +80,8 @@ void DirectConnectWindow::Connect() { // Store settings UISettings::values.nickname = ui->nickname->text(); UISettings::values.ip = ui->ip->text(); - UISettings::values.port = (ui->port->isModified() && !ui->port->text().isEmpty()) - ? ui->port->text() - : UISettings::values.port; + UISettings::values.port = + !ui->port->text().isEmpty() ? ui->port->text() : UISettings::values.port; // attempt to connect in a different thread QFuture f = QtConcurrent::run([&] { diff --git a/src/citra_qt/multiplayer/lobby.cpp b/src/citra_qt/multiplayer/lobby.cpp index 7cd2ae7e1f..00671b8420 100644 --- a/src/citra_qt/multiplayer/lobby.cpp +++ b/src/citra_qt/multiplayer/lobby.cpp @@ -63,10 +63,10 @@ Lobby::Lobby(Core::System& system_, QWidget* parent, QStandardItemModel* list, // UI Buttons connect(ui->refresh_list, &QPushButton::clicked, this, &Lobby::RefreshLobby); + connect(ui->search, &QLineEdit::textChanged, proxy, &LobbyFilterProxyModel::SetFilterSearch); connect(ui->games_owned, &QCheckBox::toggled, proxy, &LobbyFilterProxyModel::SetFilterOwned); connect(ui->hide_empty, &QCheckBox::toggled, proxy, &LobbyFilterProxyModel::SetFilterEmpty); connect(ui->hide_full, &QCheckBox::toggled, proxy, &LobbyFilterProxyModel::SetFilterFull); - connect(ui->search, &QLineEdit::textChanged, proxy, &LobbyFilterProxyModel::SetFilterSearch); connect(ui->room_list, &QTreeView::doubleClicked, this, &Lobby::OnJoinRoom); connect(ui->room_list, &QTreeView::clicked, this, &Lobby::OnExpandRoom); @@ -74,6 +74,12 @@ Lobby::Lobby(Core::System& system_, QWidget* parent, QStandardItemModel* list, connect(&room_list_watcher, &QFutureWatcher::finished, this, &Lobby::OnRefreshLobby); + // Load persistent filters after events are connected to make sure they apply + ui->search->setText(UISettings::values.multiplayer_filter_text); + ui->games_owned->setChecked(UISettings::values.multiplayer_filter_games_owned); + ui->hide_empty->setChecked(UISettings::values.multiplayer_filter_hide_empty); + ui->hide_full->setChecked(UISettings::values.multiplayer_filter_hide_full); + // manually start a refresh when the window is opening // TODO(jroweboy): if this refresh is slow for people with bad internet, then don't do it as // part of the constructor, but offload the refresh until after the window shown. perhaps emit a @@ -180,6 +186,10 @@ void Lobby::OnJoinRoom(const QModelIndex& source) { UISettings::values.nickname = ui->nickname->text(); UISettings::values.ip = proxy->data(connection_index, LobbyItemHost::HostIPRole).toString(); UISettings::values.port = proxy->data(connection_index, LobbyItemHost::HostPortRole).toString(); + UISettings::values.multiplayer_filter_text = ui->search->text(); + UISettings::values.multiplayer_filter_games_owned = ui->games_owned->isChecked(); + UISettings::values.multiplayer_filter_hide_empty = ui->hide_empty->isChecked(); + UISettings::values.multiplayer_filter_hide_full = ui->hide_full->isChecked(); } void Lobby::ResetModel() { diff --git a/src/citra_qt/multiplayer/lobby_p.h b/src/citra_qt/multiplayer/lobby_p.h index 16db78eae5..f1890fdefc 100644 --- a/src/citra_qt/multiplayer/lobby_p.h +++ b/src/citra_qt/multiplayer/lobby_p.h @@ -188,12 +188,37 @@ public: } QVariant data(int role) const override { - if (role != Qt::DisplayRole) { + switch (role) { + case Qt::DisplayRole: { + auto members = data(MemberListRole).toList(); + return QStringLiteral("%1 / %2").arg(QString::number(members.size()), + data(MaxPlayerRole).toString()); + } + case Qt::ForegroundRole: { + auto members = data(MemberListRole).toList(); + auto max_players = data(MaxPlayerRole).toInt(); + const QColor room_full_color(255, 48, 32); + const QColor room_almost_full_color(255, 140, 32); + const QColor room_has_players_color(32, 160, 32); + const QColor room_empty_color(128, 128, 128); + + if (members.size() >= max_players) { + return QBrush(room_full_color); + } else if (members.size() == (max_players - 1)) { + return QBrush(room_almost_full_color); + } else if (members.size() == 0) { + return QBrush(room_empty_color); + } else if (members.size() > 0 && members.size() < (max_players - 1)) { + return QBrush(room_has_players_color); + } + + // FIXME: How to return a value that tells Qt not to modify the + // text color from the default (as if Qt::ForegroundRole wasn't overridden)? + return QBrush(nullptr); + } + default: return LobbyItem::data(role); } - auto members = data(MemberListRole).toList(); - return QStringLiteral("%1 / %2").arg(QString::number(members.size()), - data(MaxPlayerRole).toString()); } bool operator<(const QStandardItem& other) const override { diff --git a/src/citra_qt/uisettings.h b/src/citra_qt/uisettings.h index 8e671f49f2..6dedff0f00 100644 --- a/src/citra_qt/uisettings.h +++ b/src/citra_qt/uisettings.h @@ -138,6 +138,11 @@ struct Values { QString room_description; std::pair, std::vector> ban_list; + QString multiplayer_filter_text; + bool multiplayer_filter_games_owned; + bool multiplayer_filter_hide_empty; + bool multiplayer_filter_hide_full; + // logging Settings::Setting show_console{false, "showConsole"}; };