diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index 75d1fb0fbf..b807759621 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -117,6 +117,8 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) { default_theme_paths = QIcon::themeSearchPaths(); UpdateUITheme(); + Network::Init(); + InitializeWidgets(); InitializeDebugWidgets(); InitializeRecentFileMenuActions(); @@ -131,8 +133,6 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) { SetupUIStrings(); - Network::Init(); - setWindowTitle(QString("Citra %1| %2-%3") .arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc)); show(); diff --git a/src/citra_qt/multiplayer/client_room.cpp b/src/citra_qt/multiplayer/client_room.cpp index 8baafb4187..b4737eb3b9 100644 --- a/src/citra_qt/multiplayer/client_room.cpp +++ b/src/citra_qt/multiplayer/client_room.cpp @@ -96,6 +96,8 @@ void ClientRoomWindow::Disconnect() { if (auto member = Network::GetRoomMember().lock()) { member->Leave(); ui->chat->AppendStatusMessage(tr("Disconnected")); + close(); + emit Closed(); } } diff --git a/src/citra_qt/multiplayer/host_room.cpp b/src/citra_qt/multiplayer/host_room.cpp index df43b23b2f..09958e5964 100644 --- a/src/citra_qt/multiplayer/host_room.cpp +++ b/src/citra_qt/multiplayer/host_room.cpp @@ -79,6 +79,8 @@ void HostRoomWindow::Host() { return; } else { member->Leave(); + auto parent = static_cast(parentWidget()); + parent->OnCloseRoom(); } } ui->host->setDisabled(true); diff --git a/src/citra_qt/multiplayer/lobby.cpp b/src/citra_qt/multiplayer/lobby.cpp index 3e21738c29..91ba080fbe 100644 --- a/src/citra_qt/multiplayer/lobby.cpp +++ b/src/citra_qt/multiplayer/lobby.cpp @@ -35,7 +35,7 @@ Lobby::Lobby(QWidget* parent, QStandardItemModel* list, proxy->setFilterCaseSensitivity(Qt::CaseInsensitive); proxy->setSortLocaleAware(true); ui->room_list->setModel(proxy); - ui->room_list->header()->setSectionResizeMode(QHeaderView::ResizeToContents); + ui->room_list->header()->setSectionResizeMode(QHeaderView::Interactive); ui->room_list->header()->stretchLastSection(); ui->room_list->setAlternatingRowColors(true); ui->room_list->setSelectionMode(QHeaderView::SingleSelection); @@ -45,7 +45,7 @@ Lobby::Lobby(QWidget* parent, QStandardItemModel* list, ui->room_list->setSortingEnabled(true); ui->room_list->setEditTriggers(QHeaderView::NoEditTriggers); ui->room_list->setExpandsOnDoubleClick(false); - ui->room_list->setUniformRowHeights(true); + // ui->room_list->setUniformRowHeights(true); ui->room_list->setContextMenuPolicy(Qt::CustomContextMenu); ui->nickname->setValidator(Validation::nickname); @@ -61,6 +61,7 @@ Lobby::Lobby(QWidget* parent, QStandardItemModel* list, connect(ui->search, &QLineEdit::textChanged, proxy, &LobbyFilterProxyModel::setFilterFixedString); connect(ui->room_list, &QTreeView::doubleClicked, this, &Lobby::OnJoinRoom); + connect(ui->room_list, &QTreeView::clicked, this, &Lobby::OnExpandRoom); // Actions connect(this, &Lobby::LobbyRefreshed, this, &Lobby::OnRefreshLobby); @@ -99,6 +100,11 @@ const QString Lobby::PasswordPrompt() { return ok ? text : QString(); } +void Lobby::OnExpandRoom(const QModelIndex& index) { + QModelIndex member_index = proxy->index(index.row(), Column::MEMBER); + auto member_list = proxy->data(member_index, LobbyItemMemberList::MemberListRole).toList(); +} + void Lobby::OnJoinRoom(const QModelIndex& index) { if (!ui->nickname->hasAcceptableInput()) { NetworkMessage::ShowError(NetworkMessage::USERNAME_NOT_VALID); @@ -123,7 +129,6 @@ void Lobby::OnJoinRoom(const QModelIndex& index) { // attempt to connect in a different thread QFuture f = QtConcurrent::run([&, password] { if (auto room_member = Network::GetRoomMember().lock()) { - QModelIndex connection_index = proxy->index(index.row(), Column::HOST); const std::string nickname = ui->nickname->text().toStdString(); const std::string ip = @@ -161,7 +166,6 @@ void Lobby::ResetModel() { model->setHeaderData(Column::GAME_NAME, Qt::Horizontal, tr("Preferred Game"), Qt::DisplayRole); model->setHeaderData(Column::HOST, Qt::Horizontal, tr("Host"), Qt::DisplayRole); model->setHeaderData(Column::MEMBER, Qt::Horizontal, tr("Players"), Qt::DisplayRole); - ui->room_list->header()->stretchLastSection(); } void Lobby::RefreshLobby() { @@ -181,7 +185,7 @@ void Lobby::OnRefreshLobby() { // find the icon for the game if this person owns that game. QPixmap smdh_icon; for (int r = 0; r < game_list->rowCount(); ++r) { - auto index = QModelIndex(game_list->index(r, 0)); + auto index = game_list->index(r, 0); auto game_id = game_list->data(index, GameListItemPath::ProgramIdRole).toULongLong(); if (game_id != 0 && room.preferred_game_id == game_id) { smdh_icon = game_list->data(index, Qt::DecorationRole).value(); @@ -196,17 +200,41 @@ void Lobby::OnRefreshLobby() { members.append(var); } - model->appendRow(QList( - {new LobbyItemPassword(room.has_password), - new LobbyItemName(QString::fromStdString(room.name)), - new LobbyItemGame(room.preferred_game_id, QString::fromStdString(room.preferred_game), - smdh_icon), - new LobbyItemHost(QString::fromStdString(room.owner), QString::fromStdString(room.ip), - room.port), - new LobbyItemMemberList(members, room.max_player)})); + auto first_item = new LobbyItemPassword(room.has_password); + auto row = QList({ + first_item, + new LobbyItemName(QString::fromStdString(room.name)), + new LobbyItemGame(room.preferred_game_id, QString::fromStdString(room.preferred_game), + smdh_icon), + new LobbyItemHost(QString::fromStdString(room.owner), QString::fromStdString(room.ip), + room.port), + new LobbyItemMemberList(members, room.max_player), + }); + model->appendRow(row); + // To make the rows expandable, add the member data as a child of the first column of the + // rows with people in them and have qt set them to colspan after the model is finished + // resetting + if (room.members.size() > 0) { + first_item->appendRow(new LobbyItemExpandedMemberList(members)); + } } + ui->room_list->setModel(model); + + // Reenable the refresh button and resize the columns ui->refresh_list->setEnabled(true); ui->refresh_list->setText(tr("Refresh List")); + ui->room_list->header()->stretchLastSection(); + for (int i = 0; i < Column::TOTAL - 1; ++i) { + ui->room_list->resizeColumnToContents(i); + } + + // Set the member list child items to span all columns + for (int i = 0; i < model->rowCount(); i++) { + auto parent = model->item(i, 0); + if (parent->hasChildren()) { + ui->room_list->setFirstColumnSpanned(0, parent->index(), true); + } + } } LobbyFilterProxyModel::LobbyFilterProxyModel(QWidget* parent, QStandardItemModel* list) diff --git a/src/citra_qt/multiplayer/lobby.h b/src/citra_qt/multiplayer/lobby.h index d2141b6cad..c68a7fd7b5 100644 --- a/src/citra_qt/multiplayer/lobby.h +++ b/src/citra_qt/multiplayer/lobby.h @@ -46,6 +46,14 @@ private slots: */ void OnRefreshLobby(); + /** + * Handler for single clicking on a room in the list. Expands the treeitem to show player + * information for the people in the room + * + * index - The row of the proxy model that the user wants to join. + */ + void OnExpandRoom(const QModelIndex&); + /** * Handler for double clicking on a room in the list. Gathers the host ip and port and attempts * to connect. Will also prompt for a password in case one is required. @@ -99,7 +107,7 @@ private: std::future room_list_future; std::weak_ptr announce_multiplayer_session; - std::unique_ptr ui; + Ui::Lobby* ui; QFutureWatcher* watcher; }; diff --git a/src/citra_qt/multiplayer/lobby.ui b/src/citra_qt/multiplayer/lobby.ui index e5489b18ae..b21a7c928c 100644 --- a/src/citra_qt/multiplayer/lobby.ui +++ b/src/citra_qt/multiplayer/lobby.ui @@ -6,7 +6,7 @@ 0 0 - 707 + 850 487 diff --git a/src/citra_qt/multiplayer/lobby_p.h b/src/citra_qt/multiplayer/lobby_p.h index a8a429f126..7523d881d2 100644 --- a/src/citra_qt/multiplayer/lobby_p.h +++ b/src/citra_qt/multiplayer/lobby_p.h @@ -23,16 +23,17 @@ enum List { class LobbyItem : public QStandardItem { public: - LobbyItem() : QStandardItem() {} - LobbyItem(const QString& string) : QStandardItem(string) {} + LobbyItem() = default; + explicit LobbyItem(const QString& string) : QStandardItem(string) {} virtual ~LobbyItem() override {} }; class LobbyItemPassword : public LobbyItem { public: static const int PasswordRole = Qt::UserRole + 1; - LobbyItemPassword() : LobbyItem() {} - LobbyItemPassword(const bool has_password) : LobbyItem() { + + LobbyItemPassword() = default; + explicit LobbyItemPassword(const bool has_password) : LobbyItem() { setData(has_password, PasswordRole); } @@ -52,8 +53,9 @@ public: class LobbyItemName : public LobbyItem { public: static const int NameRole = Qt::UserRole + 1; - LobbyItemName() : LobbyItem() {} - LobbyItemName(QString name) : LobbyItem() { + + LobbyItemName() = default; + explicit LobbyItemName(QString name) : LobbyItem() { setData(name, NameRole); } @@ -74,8 +76,8 @@ public: static const int GameNameRole = Qt::UserRole + 2; static const int GameIconRole = Qt::UserRole + 3; - LobbyItemGame() : LobbyItem() {} - LobbyItemGame(u64 title_id, QString game_name, QPixmap smdh_icon) : LobbyItem() { + LobbyItemGame() = default; + explicit LobbyItemGame(u64 title_id, QString game_name, QPixmap smdh_icon) : LobbyItem() { setData(static_cast(title_id), TitleIDRole); setData(game_name, GameNameRole); if (!smdh_icon.isNull()) { @@ -109,8 +111,8 @@ public: static const int HostIPRole = Qt::UserRole + 2; static const int HostPortRole = Qt::UserRole + 3; - LobbyItemHost() : LobbyItem() {} - LobbyItemHost(QString username, QString ip, u16 port) : LobbyItem() { + LobbyItemHost() = default; + explicit LobbyItemHost(QString username, QString ip, u16 port) : LobbyItem() { setData(username, HostUsernameRole); setData(ip, HostIPRole); setData(port, HostPortRole); @@ -132,15 +134,15 @@ public: class LobbyMember { public: - LobbyMember() {} + LobbyMember() = default; LobbyMember(const LobbyMember& other) { username = other.username; title_id = other.title_id; game_name = other.game_name; } - LobbyMember(const QString username, u64 title_id, const QString game_name) + explicit LobbyMember(const QString username, u64 title_id, const QString game_name) : username(username), title_id(title_id), game_name(game_name) {} - ~LobbyMember() {} + ~LobbyMember() = default; QString GetUsername() const { return username; @@ -165,8 +167,8 @@ public: static const int MemberListRole = Qt::UserRole + 1; static const int MaxPlayerRole = Qt::UserRole + 2; - LobbyItemMemberList() : LobbyItem() {} - LobbyItemMemberList(QList members, u32 max_players) : LobbyItem() { + LobbyItemMemberList() = default; + explicit LobbyItemMemberList(QList members, u32 max_players) : LobbyItem() { setData(members, MemberListRole); setData(max_players, MaxPlayerRole); } @@ -187,3 +189,34 @@ public: return left_members < right_members; } }; + +/** + * Member information for when a lobby is expanded in the UI + */ +class LobbyItemExpandedMemberList : public LobbyItem { +public: + static const int MemberListRole = Qt::UserRole + 1; + + LobbyItemExpandedMemberList() = default; + explicit LobbyItemExpandedMemberList(QList members) : LobbyItem() { + setData(members, MemberListRole); + } + + QVariant data(int role) const override { + if (role != Qt::DisplayRole) { + return LobbyItem::data(role); + } + auto members = data(MemberListRole).toList(); + QString out = QObject::tr("Current Players in the room"); + for (const auto& member : members) { + const auto& m = member.value(); + if (m.GetGameName().isEmpty()) { + out += QString(QObject::tr("\n%1 is not playing a game")).arg(m.GetUsername()); + } else { + out += QString(QObject::tr("\n%1 is playing %2")) + .arg(m.GetUsername(), m.GetGameName()); + } + } + return out; + } +}; diff --git a/src/citra_qt/multiplayer/state.cpp b/src/citra_qt/multiplayer/state.cpp index 3b072f85c7..f0857af529 100644 --- a/src/citra_qt/multiplayer/state.cpp +++ b/src/citra_qt/multiplayer/state.cpp @@ -20,13 +20,13 @@ MultiplayerState::MultiplayerState(QWidget* parent, QStandardItemModel* game_lis : QWidget(parent), game_list_model(game_list_model) { if (auto member = Network::GetRoomMember().lock()) { // register the network structs to use in slots and signals - qRegisterMetaType(); state_callback_handle = member->BindOnStateChanged( [this](const Network::RoomMember::State& state) { emit NetworkStateChanged(state); }); connect(this, &MultiplayerState::NetworkStateChanged, this, &MultiplayerState::OnNetworkStateChanged); } + qRegisterMetaType(); qRegisterMetaType(); announce_multiplayer_session = std::make_shared(); announce_multiplayer_session->BindErrorCallback( @@ -91,11 +91,6 @@ static void BringWidgetToFront(QWidget* widget) { void MultiplayerState::OnViewLobby() { if (lobby == nullptr) { lobby = new Lobby(this, game_list_model, announce_multiplayer_session); - connect(lobby, &Lobby::Closed, [&] { - LOG_INFO(Frontend, "Destroying lobby"); - // lobby->close(); - lobby = nullptr; - }); } BringWidgetToFront(lobby); } @@ -103,11 +98,6 @@ void MultiplayerState::OnViewLobby() { void MultiplayerState::OnCreateRoom() { if (host_room == nullptr) { host_room = new HostRoomWindow(this, game_list_model, announce_multiplayer_session); - connect(host_room, &HostRoomWindow::Closed, [&] { - // host_room->close(); - LOG_INFO(Frontend, "Destroying host room"); - host_room = nullptr; - }); } BringWidgetToFront(host_room); } @@ -118,7 +108,6 @@ void MultiplayerState::OnCloseRoom() { if (NetworkMessage::WarnCloseRoom()) { room->Destroy(); announce_multiplayer_session->Stop(); - // host_room->close(); } } } @@ -129,11 +118,6 @@ void MultiplayerState::OnOpenNetworkRoom() { if (member->IsConnected()) { if (client_room == nullptr) { client_room = new ClientRoomWindow(this); - connect(client_room, &ClientRoomWindow::Closed, [&] { - LOG_INFO(Frontend, "Destroying client room"); - // client_room->close(); - client_room = nullptr; - }); } BringWidgetToFront(client_room); return; @@ -147,11 +131,6 @@ void MultiplayerState::OnOpenNetworkRoom() { void MultiplayerState::OnDirectConnectToRoom() { if (direct_connect == nullptr) { direct_connect = new DirectConnectWindow(this); - connect(direct_connect, &DirectConnectWindow::Closed, [&] { - LOG_INFO(Frontend, "Destroying direct connect"); - // direct_connect->close(); - direct_connect = nullptr; - }); } BringWidgetToFront(direct_connect); }