diff --git a/src/core/fullscreenui.h b/src/core/fullscreenui.h index 61118b67b..a35989247 100644 --- a/src/core/fullscreenui.h +++ b/src/core/fullscreenui.h @@ -30,7 +30,7 @@ void OnSystemDestroyed(); void Shutdown(bool clear_state); void Render(); -void InvalidateCoverCache(); +void InvalidateCoverCache(std::string path = {}); float GetBackgroundAlpha(); diff --git a/src/core/fullscreenui_game_list.cpp b/src/core/fullscreenui_game_list.cpp index 0c0e5fcbb..b472fc5f4 100644 --- a/src/core/fullscreenui_game_list.cpp +++ b/src/core/fullscreenui_game_list.cpp @@ -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, diff --git a/src/core/fullscreenui_private.h b/src/core/fullscreenui_private.h index 269d6ea8c..246227fd2 100644 --- a/src/core/fullscreenui_private.h +++ b/src/core/fullscreenui_private.h @@ -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 diff --git a/src/core/fullscreenui_settings.cpp b/src/core/fullscreenui_settings.cpp index fb6899abb..fc3c7cad2 100644 --- a/src/core/fullscreenui_settings.cpp +++ b/src/core/fullscreenui_settings.cpp @@ -1467,7 +1467,8 @@ void FullscreenUI::DrawFolderSetting(SettingsInterface* bsi, std::string_view ti SetSettingsChanged(bsi); Host::RunOnCPUThread(&EmuFolders::Update); - ClearCoverCache(); + if (key == "Covers") + RemoveCoverCacheEntry({}); }); } } diff --git a/src/duckstation-qt/coverdownloadwindow.cpp b/src/duckstation-qt/coverdownloadwindow.cpp index 641154d85..bbce994c5 100644 --- a/src/duckstation-qt/coverdownloadwindow.cpp +++ b/src/duckstation-qt/coverdownloadwindow.cpp @@ -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) diff --git a/src/duckstation-qt/gamelistwidget.cpp b/src/duckstation-qt/gamelistwidget.cpp index 66513737b..aee39ca19 100644 --- a/src/duckstation-qt/gamelistwidget.cpp +++ b/src/duckstation-qt/gamelistwidget.cpp @@ -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& rows) @@ -537,17 +520,66 @@ void GameListModel::rowsChanged(const QList& rows) } } -void GameListModel::invalidateCoverForPath(const std::string& path) +Qt::ItemDataRole GameListModel::getRoleToInvalidate(int column) +{ + 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 */) { - std::optional row; + // 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(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(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(i)); + if (entry->path == path) { - row = static_cast(i); - break; + remove_entry(entry, static_cast(i)); + return; } } } - - if (!row.has_value()) - { - // Game removed? - return; - } - - const QModelIndex mi(index(static_cast(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) diff --git a/src/duckstation-qt/gamelistwidget.h b/src/duckstation-qt/gamelistwidget.h index 717065cf5..80ee888a7 100644 --- a/src/duckstation-qt/gamelistwidget.h +++ b/src/duckstation-qt/gamelistwidget.h @@ -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: diff --git a/src/duckstation-qt/gamesummarywidget.cpp b/src/duckstation-qt/gamesummarywidget.cpp index da720fe6e..0fb857533 100644 --- a/src/duckstation-qt/gamesummarywidget.cpp +++ b/src/duckstation-qt/gamesummarywidget.cpp @@ -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(static_cast(language - 1)) : std::optional()); - g_main_window->refreshGameListModel(); + g_main_window->getGameListWidget()->getModel()->invalidateColumnForPath(m_path, GameListModel::Column_Region); } static QString MSFToString(const CDImage::Position& position) diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index 5c19677f9..adc4d0d05 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -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& 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); }; }); } diff --git a/src/duckstation-qt/mainwindow.h b/src/duckstation-qt/mainwindow.h index ced2da94b..bfea4d1e5 100644 --- a/src/duckstation-qt/mainwindow.h +++ b/src/duckstation-qt/mainwindow.h @@ -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& func);