Qt: Simplify cover and icon invalidation

Get rid of multiple functions for doing the same thing, sync Big Picture
and Qt UIs.
This commit is contained in:
Stenzek
2025-12-13 11:11:53 +10:00
parent c9177bc398
commit 9eaf58613a
10 changed files with 97 additions and 97 deletions

View File

@@ -30,7 +30,7 @@ void OnSystemDestroyed();
void Shutdown(bool clear_state);
void Render();
void InvalidateCoverCache();
void InvalidateCoverCache(std::string path = {});
float GetBackgroundAlpha();

View File

@@ -1140,17 +1140,20 @@ void FullscreenUI::SetCoverCacheEntry(std::string path, std::string cover_path)
s_game_list_locals.cover_image_map.emplace(std::move(path), std::move(cover_path));
}
void FullscreenUI::ClearCoverCache()
void FullscreenUI::RemoveCoverCacheEntry(const std::string& path)
{
s_game_list_locals.cover_image_map.clear();
if (path.empty())
s_game_list_locals.cover_image_map.clear();
else
s_game_list_locals.cover_image_map.erase(path);
}
void FullscreenUI::InvalidateCoverCache()
void FullscreenUI::InvalidateCoverCache(std::string path)
{
if (!GPUThread::IsFullscreenUIRequested())
return;
GPUThread::RunOnThread(&FullscreenUI::ClearCoverCache);
GPUThread::RunOnThread([path = std::move(path)]() { RemoveCoverCacheEntry(path); });
}
void FullscreenUI::DrawGameListCover(const GameList::Entry* entry, bool fallback_to_achievements_icon,

View File

@@ -101,7 +101,7 @@ void DoStartPath(std::string path, std::string state = std::string(), std::optio
GPUTexture* GetCoverForCurrentGame(const std::string& game_path);
void SetCoverCacheEntry(std::string path, std::string cover_path);
void ClearCoverCache();
void RemoveCoverCacheEntry(const std::string& path);
//////////////////////////////////////////////////////////////////////////
// Settings

View File

@@ -1467,7 +1467,8 @@ void FullscreenUI::DrawFolderSetting(SettingsInterface* bsi, std::string_view ti
SetSettingsChanged(bsi);
Host::RunOnCPUThread(&EmuFolders::Update);
ClearCoverCache();
if (key == "Covers")
RemoveCoverCacheEntry({});
});
}
}

View File

@@ -2,6 +2,7 @@
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "coverdownloadwindow.h"
#include "gamelistwidget.h"
#include "mainwindow.h"
#include "qthost.h"
#include "qtprogresscallback.h"
@@ -52,7 +53,9 @@ void CoverDownloadWindow::onStartClicked()
Error error;
const bool result = GameList::DownloadCovers(
urls, use_serials, progress, &error, [](const GameList::Entry* entry, std::string) mutable {
Host::RunOnUIThread([path = entry->path]() { g_main_window->invalidateCoverCacheForPath(path); });
Host::RunOnUIThread([path = entry->path]() {
g_main_window->getGameListWidget()->getModel()->invalidateColumnForPath(path, GameListModel::Column_Cover);
});
});
return [this, result, error = std::move(error)]() { downloadComplete(result, error); };
});
@@ -66,7 +69,7 @@ void CoverDownloadWindow::onStartClicked()
void CoverDownloadWindow::downloadComplete(bool result, const Error& error)
{
g_main_window->refreshGameGridCovers();
g_main_window->getGameListWidget()->getModel()->invalidateColumn(GameListModel::Column_Cover);
m_ui.status->setText(tr("Download complete."));
if (!result)

View File

@@ -252,12 +252,7 @@ bool GameListModel::getShowLocalizedTitles() const
void GameListModel::setShowLocalizedTitles(bool enabled)
{
m_show_localized_titles = enabled;
emit dataChanged(index(0, Column_Title), index(rowCount() - 1, Column_Title), {Qt::DisplayRole, Qt::ToolTipRole});
if (m_show_titles_for_covers)
emit dataChanged(index(0, Column_Cover), index(rowCount() - 1, Column_Cover), {Qt::DisplayRole});
// emit cover changed as well since the autogenerated covers will differ
refreshCovers();
invalidateColumn(Column_Title);
}
bool GameListModel::getShowCoverTitles() const
@@ -303,13 +298,7 @@ void GameListModel::setIconSize(int size)
emit headerDataChanged(Qt::Vertical, 0, rowCount() - 1);
loadSizeDependentPixmaps();
refreshIcons();
}
void GameListModel::refreshIcons()
{
m_icon_pixmap_cache.Clear();
emit dataChanged(index(0, Column_Icon), index(rowCount() - 1, Column_Icon), {Qt::DecorationRole});
invalidateColumn(Column_Icon);
}
float GameListModel::getCoverScale() const
@@ -372,12 +361,6 @@ void GameListModel::updateCoverScale()
{Qt::DecorationRole, Qt::FontRole, Qt::SizeHintRole});
}
void GameListModel::refreshCovers()
{
m_cover_pixmap_cache.Clear();
emit dataChanged(index(0, Column_Cover), index(rowCount() - 1, Column_Cover), {Qt::DecorationRole});
}
void GameListModel::updateCacheSize(int num_rows, int num_columns, QSortFilterProxyModel* const sort_model,
int top_left_row)
{
@@ -422,8 +405,8 @@ void GameListModel::setDevicePixelRatio(qreal dpr)
m_flag_pixmap_cache.clear();
loadCommonImages();
loadCoverScaleDependentPixmaps();
refreshCovers();
refreshIcons();
invalidateColumn(Column_Icon);
invalidateColumn(Column_Cover);
}
void GameListModel::reloadThemeSpecificImages()
@@ -510,7 +493,7 @@ void GameListModel::coverLoaded(const std::string& path, const QImage& image, fl
pmp->scale = scale;
pmp->is_loading = false;
invalidateCoverForPath(path);
invalidateColumnForPath(path, Column_Cover, false);
}
void GameListModel::rowsChanged(const QList<int>& rows)
@@ -537,17 +520,66 @@ void GameListModel::rowsChanged(const QList<int>& rows)
}
}
void GameListModel::invalidateCoverForPath(const std::string& path)
Qt::ItemDataRole GameListModel::getRoleToInvalidate(int column)
{
std::optional<u32> row;
if (column == Column_Icon || column == Column_Cover || column == Column_Region)
return Qt::DecorationRole;
else
return Qt::DisplayRole;
}
void GameListModel::invalidateColumn(int column, bool invalidate_cache /* = true */)
{
if (invalidate_cache)
{
if (column == Column_Icon)
{
m_icon_pixmap_cache.Clear();
}
else if (column == Column_Cover)
{
m_cover_pixmap_cache.Clear();
if (QtHost::IsFullscreenUIStarted())
Host::RunOnCPUThread([]() { FullscreenUI::InvalidateCoverCache(); });
}
}
emit dataChanged(index(0, column), index(rowCount() - 1, column), {getRoleToInvalidate(column)});
}
void GameListModel::invalidateColumnForPath(const std::string& path, int column, bool invalidate_cache /* = true */)
{
// if we're changing the title, the cover could change
if (column == Column_Title)
invalidateColumnForPath(path, Column_Cover, invalidate_cache);
if (invalidate_cache)
{
if (column == Column_Icon)
{
m_icon_pixmap_cache.Remove(path);
}
else if (column == Column_Cover)
{
m_cover_pixmap_cache.Remove(path);
if (QtHost::IsFullscreenUIStarted())
Host::RunOnCPUThread([path]() mutable { FullscreenUI::InvalidateCoverCache(std::move(path)); });
}
}
const auto remove_entry = [this, &column](const GameList::Entry* ge, int row) {
const QModelIndex mi(index(row, column));
emit dataChanged(mi, mi, {getRoleToInvalidate(column)});
};
if (hasTakenGameList())
{
for (u32 i = 0; i < static_cast<u32>(m_taken_entries->size()); i++)
{
if (path == m_taken_entries.value()[i].path)
{
row = i;
break;
remove_entry(&m_taken_entries.value()[i], static_cast<int>(i));
return;
}
}
}
@@ -558,28 +590,14 @@ void GameListModel::invalidateCoverForPath(const std::string& path)
const size_t count = GameList::GetEntryCount();
for (size_t i = 0; i < count; i++)
{
if (GameList::GetEntryByIndex(i)->path == path)
const GameList::Entry* const entry = GameList::GetEntryByIndex(static_cast<u32>(i));
if (entry->path == path)
{
row = static_cast<int>(i);
break;
remove_entry(entry, static_cast<int>(i));
return;
}
}
}
if (!row.has_value())
{
// Game removed?
return;
}
const QModelIndex mi(index(static_cast<int>(row.value()), Column_Cover));
emit dataChanged(mi, mi, {Qt::DecorationRole});
}
void GameListModel::invalidateCoverCacheForPath(const std::string& path)
{
m_cover_pixmap_cache.Remove(path);
invalidateCoverForPath(path);
}
const QPixmap& GameListModel::getCoverForEntry(const GameList::Entry* ge) const
@@ -672,7 +690,8 @@ void GameListModel::setShowGameIcons(bool enabled)
if (enabled)
GameList::ReloadMemcardTimestampCache();
refreshIcons();
invalidateColumn(Column_Icon);
}
QIcon GameListModel::getIconForGame(const QString& path)
@@ -2053,12 +2072,6 @@ void GameListWidget::onGridViewContextMenuRequested(const QPoint& point)
emit entryContextMenuRequested(m_grid_view->mapToGlobal(point));
}
void GameListWidget::refreshGridCovers()
{
m_model->refreshCovers();
Host::RunOnCPUThread(&FullscreenUI::InvalidateCoverCache);
}
void GameListWidget::focusSearchWidget()
{
m_ui.searchText->setFocus(Qt::ShortcutFocusReason);
@@ -2110,7 +2123,6 @@ void GameListWidget::setMergeDiscSets(bool enabled)
Host::SetBaseBoolSettingValue("UI", "GameListMergeDiscSets", enabled);
Host::CommitBaseSettingChanges();
m_sort_model->setMergeDiscSets(enabled);
m_model->refreshIcons();
}
void GameListWidget::setShowLocalizedTitles(bool enabled)
@@ -2160,7 +2172,7 @@ void GameListWidget::setPreferAchievementGameIcons(bool enabled)
if (!enabled)
GameList::ReloadMemcardTimestampCache();
m_model->refreshIcons();
m_model->invalidateColumn(GameListModel::Column_Icon);
}
void GameListWidget::setShowCoverTitles(bool enabled)

View File

@@ -102,7 +102,6 @@ public:
int getIconSize() const;
int getIconSizeWithPadding() const;
void refreshIcons();
void setIconSize(int size);
bool getShowGameIcons() const;
@@ -116,7 +115,6 @@ public:
QSize getDeviceScaledCoverArtSize() const;
int getCoverArtSpacing() const;
QFont getCoverCaptionFont() const;
void refreshCovers();
void updateCacheSize(int num_rows, int num_columns, QSortFilterProxyModel* const sort_model, int top_left_row);
qreal getDevicePixelRatio() const;
@@ -125,7 +123,9 @@ public:
const QPixmap* lookupIconPixmapForEntry(const GameList::Entry* ge) const;
const QPixmap& getCoverForEntry(const GameList::Entry* ge) const;
void invalidateCoverCacheForPath(const std::string& path);
void invalidateColumn(int column, bool invalidate_cache = true);
void invalidateColumnForPath(const std::string& path, int column, bool invalidate_cache = true);
Q_SIGNALS:
void coverScaleChanged(float scale);
@@ -146,7 +146,6 @@ private:
void updateCoverScale();
void loadCoverScaleDependentPixmaps();
void loadOrGenerateCover(const GameList::Entry* ge);
void invalidateCoverForPath(const std::string& path);
void coverLoaded(const std::string& path, const QImage& image, float scale);
static void loadOrGenerateCover(QImage& image, const QImage& placeholder_image, const QSize& size, float scale,
@@ -155,6 +154,8 @@ private:
static void createPlaceholderImage(QImage& image, const QImage& placeholder_image, const QSize& size, float scale,
const QString& title);
static Qt::ItemDataRole getRoleToInvalidate(int column);
const QPixmap& getIconPixmapForEntry(const GameList::Entry* ge) const;
const QPixmap& getFlagPixmapForEntry(const GameList::Entry* ge) const;
@@ -284,7 +285,6 @@ public:
void setAnimateGameIcons(bool enabled);
void setPreferAchievementGameIcons(bool enabled);
void setShowCoverTitles(bool enabled);
void refreshGridCovers();
void focusSearchWidget();
Q_SIGNALS:

View File

@@ -2,6 +2,7 @@
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "gamesummarywidget.h"
#include "gamelistwidget.h"
#include "mainwindow.h"
#include "qthost.h"
#include "qtprogresscallback.h"
@@ -270,7 +271,7 @@ void GameSummaryWidget::setCustomTitle(const std::string& text)
}
}
g_main_window->refreshGameListModel();
g_main_window->getGameListWidget()->getModel()->invalidateColumnForPath(m_path, GameListModel::Column_Title);
}
void GameSummaryWidget::setCustomRegion(int region)
@@ -289,7 +290,7 @@ void GameSummaryWidget::setCustomRegion(int region)
}
}
g_main_window->refreshGameListModel();
g_main_window->getGameListWidget()->getModel()->invalidateColumnForPath(m_path, GameListModel::Column_Region);
}
void GameSummaryWidget::onCustomLanguageChanged(int language)
@@ -298,7 +299,7 @@ void GameSummaryWidget::onCustomLanguageChanged(int language)
m_path, (language > 0) ? std::optional<GameDatabase::Language>(static_cast<GameDatabase::Language>(language - 1)) :
std::optional<GameDatabase::Language>());
g_main_window->refreshGameListModel();
g_main_window->getGameListWidget()->getModel()->invalidateColumnForPath(m_path, GameListModel::Column_Region);
}
static QString MSFToString(const CDImage::Position& position)

View File

@@ -1753,7 +1753,8 @@ void MainWindow::setGameListEntryCoverImage(const GameList::Entry* entry)
tr("Failed to remove '%1'").arg(old_filename));
return;
}
m_game_list_widget->refreshGridCovers();
m_game_list_widget->getModel()->invalidateColumnForPath(entry->path, GameListModel::Column_Cover);
}
void MainWindow::clearGameListEntryPlayTime(const GameList::Entry* entry)
@@ -2501,8 +2502,8 @@ void MainWindow::connectSignals()
connect(m_ui.actionGridViewShowTitles, &QAction::triggered, m_game_list_widget, &GameListWidget::setShowCoverTitles);
connect(m_ui.actionViewZoomIn, &QAction::triggered, this, &MainWindow::onViewZoomInActionTriggered);
connect(m_ui.actionViewZoomOut, &QAction::triggered, this, &MainWindow::onViewZoomOutActionTriggered);
connect(m_ui.actionGridViewRefreshCovers, &QAction::triggered, m_game_list_widget,
&GameListWidget::refreshGridCovers);
connect(m_ui.actionGridViewRefreshCovers, &QAction::triggered, this,
[this]() { m_game_list_widget->getModel()->invalidateColumn(GameListModel::Column_Cover); });
connect(m_ui.actionChangeGameListBackground, &QAction::triggered, this,
&MainWindow::onViewChangeGameListBackgroundTriggered);
connect(m_ui.actionClearGameListBackground, &QAction::triggered, this,
@@ -2944,11 +2945,6 @@ void MainWindow::refreshGameList(bool invalidate_cache)
m_game_list_widget->refresh(invalidate_cache);
}
void MainWindow::refreshGameListModel()
{
m_game_list_widget->getModel()->refresh();
}
void MainWindow::cancelGameListRefresh()
{
m_game_list_widget->cancelRefresh();
@@ -2959,16 +2955,6 @@ QIcon MainWindow::getIconForGame(const QString& path)
return m_game_list_widget->getModel()->getIconForGame(path);
}
void MainWindow::invalidateCoverCacheForPath(const std::string& path)
{
m_game_list_widget->getModel()->invalidateCoverCacheForPath(path);
}
void MainWindow::refreshGameGridCovers()
{
m_game_list_widget->getModel()->refreshCovers();
}
void MainWindow::runOnUIThread(const std::function<void()>& func)
{
func();
@@ -3293,11 +3279,9 @@ void MainWindow::onToolsDownloadAchievementGameIconsTriggered()
const bool result = Achievements::DownloadGameIcons(progress, &error);
return [error = std::move(error), result]() {
if (!result)
{
g_main_window->reportError(tr("Error"), QString::fromStdString(error.GetDescription()));
}
g_main_window->refreshGameListModel();
g_main_window->m_game_list_widget->getModel()->invalidateColumn(GameListModel::Column_Icon);
};
});
}
@@ -3311,12 +3295,10 @@ void MainWindow::refreshAchievementProgress()
const bool result = Achievements::RefreshAllProgressDatabase(progress, &error);
return [error = std::move(error), result]() {
if (!result)
{
g_main_window->reportError(tr("Error"), QString::fromStdString(error.GetDescription()));
}
g_main_window->m_ui.statusBar->showMessage(tr("RA: Updated achievement progress database."));
g_main_window->refreshGameListModel();
g_main_window->m_game_list_widget->getModel()->invalidateColumn(GameListModel::Column_Achievements);
};
});
}

View File

@@ -106,6 +106,7 @@ public:
ALWAYS_INLINE QLabel* getStatusResolutionWidget() const { return m_status_resolution_widget; }
ALWAYS_INLINE QLabel* getStatusFPSWidget() const { return m_status_fps_widget; }
ALWAYS_INLINE QLabel* getStatusVPSWidget() const { return m_status_vps_widget; }
ALWAYS_INLINE GameListWidget* getGameListWidget() const { return m_game_list_widget; }
ALWAYS_INLINE DebuggerWindow* getDebuggerWindow() const { return m_debugger_window; }
/// Opens the editor for a specific input profile.
@@ -120,11 +121,8 @@ public:
void updateDebugMenuVisibility();
void refreshGameList(bool invalidate_cache);
void refreshGameListModel();
void cancelGameListRefresh();
QIcon getIconForGame(const QString& path);
void invalidateCoverCacheForPath(const std::string& path);
void refreshGameGridCovers();
void refreshAchievementProgress();
void runOnUIThread(const std::function<void()>& func);