diff --git a/Source/Core/DolphinQt/CMakeLists.txt b/Source/Core/DolphinQt/CMakeLists.txt index e7e70c508b..b0c6829de2 100644 --- a/Source/Core/DolphinQt/CMakeLists.txt +++ b/Source/Core/DolphinQt/CMakeLists.txt @@ -265,6 +265,8 @@ add_executable(dolphin-emu FIFO/FIFOAnalyzer.h FIFO/FIFOPlayerWindow.cpp FIFO/FIFOPlayerWindow.h + GameCount.cpp + GameCount.h GameList/GameList.cpp GameList/GameList.h GameList/GameListModel.cpp diff --git a/Source/Core/DolphinQt/DolphinQt.vcxproj b/Source/Core/DolphinQt/DolphinQt.vcxproj index a9459c971a..1a3cdfef23 100644 --- a/Source/Core/DolphinQt/DolphinQt.vcxproj +++ b/Source/Core/DolphinQt/DolphinQt.vcxproj @@ -165,6 +165,7 @@ + @@ -395,6 +396,7 @@ + diff --git a/Source/Core/DolphinQt/GameCount.cpp b/Source/Core/DolphinQt/GameCount.cpp new file mode 100644 index 0000000000..081d8a26df --- /dev/null +++ b/Source/Core/DolphinQt/GameCount.cpp @@ -0,0 +1,33 @@ +// Copyright 2026 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "DolphinQt/GameCount.h" + +#include +#include +#include + +GameCount::GameCount(QWidget* const parent) : QStatusBar(parent) +{ + setSizeGripEnabled(false); + QFontMetrics font_metrics(font()); + const int margin = font_metrics.height() / 5; + layout()->setContentsMargins(margin, margin, margin, margin); +} + +void GameCount::OnGameCountUpdated(const int total_games, const int visible_games) +{ + const int filtered_games = total_games - visible_games; + + if (filtered_games > 0) + { + showMessage(tr("%1 game(s) in your collection (%2 visible, %3 filtered)") + .arg(total_games) + .arg(visible_games) + .arg(filtered_games)); + } + else + { + showMessage(tr("%1 game(s) in your collection").arg(total_games)); + } +} diff --git a/Source/Core/DolphinQt/GameCount.h b/Source/Core/DolphinQt/GameCount.h new file mode 100644 index 0000000000..19ad0c1a98 --- /dev/null +++ b/Source/Core/DolphinQt/GameCount.h @@ -0,0 +1,15 @@ +// Copyright 2026 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +class GameCount : public QStatusBar +{ + Q_OBJECT +public: + explicit GameCount(QWidget* parent = nullptr); + + void OnGameCountUpdated(int total_games, int visible_games); +}; diff --git a/Source/Core/DolphinQt/GameList/GameList.cpp b/Source/Core/DolphinQt/GameList/GameList.cpp index c63230aa55..dad4bc2681 100644 --- a/Source/Core/DolphinQt/GameList/GameList.cpp +++ b/Source/Core/DolphinQt/GameList/GameList.cpp @@ -139,6 +139,8 @@ GameList::GameList(QWidget* parent) : QStackedWidget(parent), m_model(this) connect(m_grid, &QListView::doubleClicked, this, &GameList::GameSelected); connect(&m_model, &QAbstractItemModel::rowsInserted, this, &GameList::ConsiderViewChange); connect(&m_model, &QAbstractItemModel::rowsRemoved, this, &GameList::ConsiderViewChange); + connect(&m_model, &QAbstractItemModel::rowsInserted, this, &GameList::UpdateGameCount); + connect(&m_model, &QAbstractItemModel::rowsRemoved, this, &GameList::UpdateGameCount); addWidget(m_list); addWidget(m_grid); @@ -1043,6 +1045,8 @@ void GameList::OnGameListVisibilityChanged() { m_list_proxy->invalidate(); m_grid_proxy->invalidate(); + + UpdateGameCount(); } void GameList::OnSectionResized(int index, int, int) @@ -1171,6 +1175,15 @@ void GameList::SetSearchTerm(const QString& term) m_grid_proxy->invalidate(); UpdateColumnVisibility(); + UpdateGameCount(); +} + +void GameList::UpdateGameCount() const +{ + const int total_games = m_model.rowCount(QModelIndex{}); + const int visible_games = m_list_proxy->rowCount(); + + emit GameCountUpdated(total_games, visible_games); } void GameList::ZoomIn() diff --git a/Source/Core/DolphinQt/GameList/GameList.h b/Source/Core/DolphinQt/GameList/GameList.h index df771bba87..94de2eec35 100644 --- a/Source/Core/DolphinQt/GameList/GameList.h +++ b/Source/Core/DolphinQt/GameList/GameList.h @@ -43,6 +43,8 @@ public: void OnColumnVisibilityToggled(const QString& row, bool visible); void OnGameListVisibilityChanged(); + void UpdateGameCount() const; + void resizeEvent(QResizeEvent* event) override; void PurgeCache(); @@ -51,6 +53,7 @@ public: signals: void GameSelected(); + void GameCountUpdated(int total_games, int visible_games) const; void OnStartWithRiivolution(const UICommon::GameFile& game); void NetPlayHost(const UICommon::GameFile& game); void SelectionChanged(std::shared_ptr game_file); diff --git a/Source/Core/DolphinQt/MainWindow.cpp b/Source/Core/DolphinQt/MainWindow.cpp index 962f75e96c..c3f5b4148c 100644 --- a/Source/Core/DolphinQt/MainWindow.cpp +++ b/Source/Core/DolphinQt/MainWindow.cpp @@ -95,6 +95,7 @@ #include "DolphinQt/EmulatedUSB/WiiSpeakWindow.h" #include "DolphinQt/FIFO/FIFOPlayerWindow.h" #include "DolphinQt/GCMemcardManager.h" +#include "DolphinQt/GameCount.h" #include "DolphinQt/GameList/GameList.h" #include "DolphinQt/Host.h" #include "DolphinQt/HotkeyScheduler.h" @@ -449,6 +450,7 @@ void MainWindow::CreateComponents() m_menu_bar = new MenuBar(this); m_tool_bar = new ToolBar(this); m_search_bar = new SearchBar(this); + m_game_count = new GameCount(this); m_game_list = new GameList(this); m_render_widget = new RenderWidget; m_stack = new QStackedWidget(this); @@ -738,9 +740,30 @@ void MainWindow::ConnectStack() layout->addWidget(m_game_list); layout->addWidget(m_search_bar); + layout->addWidget(m_game_count); + layout->setSpacing(0); layout->setContentsMargins(0, 0, 0, 0); connect(m_search_bar, &SearchBar::Search, m_game_list, &GameList::SetSearchTerm); + connect(m_game_list, &GameList::GameCountUpdated, m_game_count, &GameCount::OnGameCountUpdated); + + m_game_list->UpdateGameCount(); + + const auto update_spacing = [this](const bool game_count_is_visible) { + // The bottom margin of the search bar and the top margin of the game count are both suitable + // when the other widget is hidden, but when both are visible the gap created by the combination + // is too large. To fix this we set the bottom margin of the search bar to 0 when the game count + // is visible and set it to the top margin when the game count is hidden. + m_game_count->setVisible(game_count_is_visible); + auto* const search_layout = m_search_bar->layout(); + QMargins search_margins = search_layout->contentsMargins(); + const int new_bottom_margin = game_count_is_visible ? 0 : search_margins.top(); + search_margins.setBottom(new_bottom_margin); + search_layout->setContentsMargins(search_margins); + }; + update_spacing(Settings::Instance().IsGameCountVisible()); + + connect(&Settings::Instance(), &Settings::GameCountVisibilityChanged, update_spacing); m_stack->addWidget(widget); diff --git a/Source/Core/DolphinQt/MainWindow.h b/Source/Core/DolphinQt/MainWindow.h index c0d5bd110e..93490eaad6 100644 --- a/Source/Core/DolphinQt/MainWindow.h +++ b/Source/Core/DolphinQt/MainWindow.h @@ -31,6 +31,7 @@ class CodeWidget; class DiscordHandler; class DragEnterEvent; class FreeLookWindow; +class GameCount; class GameList; class GBATASInputWindow; class GCTASInputWindow; @@ -241,6 +242,7 @@ private: MenuBar* m_menu_bar; SearchBar* m_search_bar; GameList* m_game_list; + GameCount* m_game_count; RenderWidget* m_render_widget = nullptr; bool m_rendering_to_main; bool m_stop_confirm_showing = false; diff --git a/Source/Core/DolphinQt/MenuBar.cpp b/Source/Core/DolphinQt/MenuBar.cpp index 04c1b3eecd..5015eb5fee 100644 --- a/Source/Core/DolphinQt/MenuBar.cpp +++ b/Source/Core/DolphinQt/MenuBar.cpp @@ -595,6 +595,13 @@ void MenuBar::AddViewMenu() AddShowPlatformsMenu(view_menu); AddShowRegionsMenu(view_menu); + view_menu->addSeparator(); + QAction* const show_game_count = view_menu->addAction(tr("Show Game Count")); + show_game_count->setCheckable(true); + show_game_count->setChecked(Settings::Instance().IsGameCountVisible()); + connect(show_game_count, &QAction::toggled, &Settings::Instance(), + &Settings::SetGameCountVisible); + view_menu->addSeparator(); QAction* const purge_action = view_menu->addAction(tr("Purge Game List Cache"), this, &MenuBar::PurgeGameListCache); diff --git a/Source/Core/DolphinQt/Settings.cpp b/Source/Core/DolphinQt/Settings.cpp index 3046009fbb..396cd14ae5 100644 --- a/Source/Core/DolphinQt/Settings.cpp +++ b/Source/Core/DolphinQt/Settings.cpp @@ -514,6 +514,21 @@ void Settings::SetAutoRefreshEnabled(bool enabled) emit AutoRefreshToggled(enabled); } +bool Settings::IsGameCountVisible() const +{ + return GetQSettings().value(QStringLiteral("GameCount/Visible"), true).toBool(); +} + +void Settings::SetGameCountVisible(const bool visible) +{ + if (IsGameCountVisible() == visible) + return; + + GetQSettings().setValue(QStringLiteral("GameCount/Visible"), visible); + + emit GameCountVisibilityChanged(visible); +} + QString Settings::GetDefaultGame() const { return QString::fromStdString(Config::Get(Config::MAIN_DEFAULT_ISO)); @@ -815,6 +830,7 @@ void Settings::RefreshWidgetVisibility() emit DebugModeToggled(IsDebugModeEnabled()); emit LogVisibilityChanged(IsLogVisible()); emit LogConfigVisibilityChanged(IsLogConfigVisible()); + emit GameCountVisibilityChanged(IsGameCountVisible()); } void Settings::SetDebugFont(QFont font) diff --git a/Source/Core/DolphinQt/Settings.h b/Source/Core/DolphinQt/Settings.h index 97d3b751cd..2c721a8489 100644 --- a/Source/Core/DolphinQt/Settings.h +++ b/Source/Core/DolphinQt/Settings.h @@ -110,6 +110,8 @@ public: void ReloadTitleDB(); bool IsAutoRefreshEnabled() const; void SetAutoRefreshEnabled(bool enabled); + bool IsGameCountVisible() const; + void SetGameCountVisible(bool visible); // Emulation int GetStateSlot() const; @@ -228,6 +230,7 @@ signals: void DevicesChanged(); void WiiSpeakMuteChanged(bool muted); void EnableGfxModsChanged(bool enabled); + void GameCountVisibilityChanged(bool visible); private: Settings();