Qt: kind of lazy loading icons

This commit is contained in:
Megamouse 2021-04-16 03:53:31 +02:00
parent b5f1f50a16
commit f5366c91a7
4 changed files with 276 additions and 213 deletions

View file

@ -37,7 +37,7 @@ void game_list::mouseMoveEvent(QMouseEvent *event)
m_last_hover_item = new_item;
}
void game_list::leaveEvent(QEvent *event)
void game_list::leaveEvent(QEvent */*event*/)
{
if (m_last_hover_item)
{

View file

@ -7,6 +7,8 @@
#include "game_compatibility.h"
#include "Emu/GameInfo.h"
class movie_item;
/* Having the icons associated with the game info simplifies logic internally */
struct gui_game_info
{
@ -18,13 +20,12 @@ struct gui_game_info
bool hasCustomConfig;
bool hasCustomPadConfig;
bool has_hover_gif;
movie_item* item;
};
typedef std::shared_ptr<gui_game_info> game_info;
Q_DECLARE_METATYPE(game_info)
class movie_item;
/*
class used in order to get deselection and hover change
if you know a simpler way, tell @Megamouse

View file

@ -19,7 +19,6 @@
#include "Emu/system_utils.hpp"
#include "Loader/PSF.h"
#include "util/types.hpp"
#include "Utilities/lockless.h"
#include "Utilities/File.h"
#include "util/yaml.hpp"
#include "Input/pad_thread.h"
@ -131,8 +130,26 @@ game_list_frame::game_list_frame(std::shared_ptr<gui_settings> gui_settings, std
add_column(gui::column_compat, tr("Compatibility"), tr("Show Compatibility"));
// Events
connect(&m_refresh_watcher, &QFutureWatcher<void>::finished, this, &game_list_frame::OnRefreshFinished);
connect(&m_refresh_watcher, &QFutureWatcher<void>::canceled, this, [this]()
{
m_path_list.clear();
m_game_data.clear();
m_serials.clear();
m_games.pop_all();
});
connect(&m_repaint_watcher, &QFutureWatcher<movie_item*>::finished, this, &game_list_frame::OnRepaintFinished);
connect(&m_repaint_watcher, &QFutureWatcher<movie_item*>::resultReadyAt, this, [this](int index)
{
if (!m_is_list_layout) return;
if (movie_item* item = m_repaint_watcher.resultAt(index))
{
item->call_icon_func();
}
});
connect(m_game_list, &QTableWidget::customContextMenuRequested, this, &game_list_frame::ShowContextMenu);
connect(m_game_list, &QTableWidget::itemSelectionChanged, this, &game_list_frame::itemSelectionChangedSlot);
connect(m_game_list, &QTableWidget::itemSelectionChanged, this, &game_list_frame::ItemSelectionChangedSlot);
connect(m_game_list, &QTableWidget::itemDoubleClicked, this, &game_list_frame::doubleClickedSlot);
connect(m_game_list->horizontalHeader(), &QHeaderView::sectionClicked, this, &game_list_frame::OnColClicked);
@ -144,7 +161,7 @@ game_list_frame::game_list_frame(std::shared_ptr<gui_settings> gui_settings, std
});
connect(m_game_grid, &QTableWidget::customContextMenuRequested, this, &game_list_frame::ShowContextMenu);
connect(m_game_grid, &QTableWidget::itemSelectionChanged, this, &game_list_frame::itemSelectionChangedSlot);
connect(m_game_grid, &QTableWidget::itemSelectionChanged, this, &game_list_frame::ItemSelectionChangedSlot);
connect(m_game_grid, &QTableWidget::itemDoubleClicked, this, &game_list_frame::doubleClickedSlot);
connect(m_game_compat, &game_compatibility::DownloadStarted, [this]()
@ -405,20 +422,29 @@ std::string game_list_frame::GetDataDirBySerial(const std::string& serial)
void game_list_frame::Refresh(const bool from_drive, const bool scroll_after)
{
if (m_repaint_watcher.isRunning())
{
m_repaint_watcher.cancel();
m_repaint_watcher.waitForFinished();
}
if (m_refresh_watcher.isRunning())
{
m_refresh_watcher.cancel();
m_refresh_watcher.waitForFinished();
}
if (from_drive)
{
const Localized localized;
// Load PSF
m_path_list.clear();
m_serials.clear();
m_game_data.clear();
m_notes.clear();
m_games.pop_all();
const std::string _hdd = rpcs3::utils::get_hdd0_dir();
const std::string cat_unknown = sstr(cat::cat_unknown);
const std::string cat_unknown_localized = sstr(localized.category.unknown);
std::vector<std::string> path_list;
const auto add_disc_dir = [&](const std::string& path)
{
@ -431,7 +457,7 @@ void game_list_frame::Refresh(const bool from_drive, const bool scroll_after)
if (entry.name == "PS3_GAME" || std::regex_match(entry.name, std::regex("^PS3_GM[[:digit:]]{2}$")))
{
path_list.emplace_back(path + "/" + entry.name);
m_path_list.emplace_back(path + "/" + entry.name);
}
}
};
@ -466,7 +492,7 @@ void game_list_frame::Refresh(const bool from_drive, const bool scroll_after)
}
else
{
path_list.emplace_back(entry_path);
m_path_list.emplace_back(entry_path);
}
}
}
@ -530,23 +556,17 @@ void game_list_frame::Refresh(const bool from_drive, const bool scroll_after)
}
// Remove duplicates
sort(path_list.begin(), path_list.end());
path_list.erase(unique(path_list.begin(), path_list.end()), path_list.end());
sort(m_path_list.begin(), m_path_list.end());
m_path_list.erase(unique(m_path_list.begin(), m_path_list.end()), m_path_list.end());
QSet<QString> serials;
QMutex mutex_cat;
lf_queue<game_info> games;
const std::string game_icon_path = m_play_hover_movies ? fs::get_config_dir() + "/Icons/game_icons/" : "";
QtConcurrent::blockingMap(path_list, [&](const std::string& dir)
m_refresh_watcher.setFuture(QtConcurrent::map(m_path_list, [this, cat_unknown_localized = sstr(localized.category.unknown), cat_unknown = sstr(cat::cat_unknown), game_icon_path](const std::string& dir)
{
const Localized thread_localized;
{
const std::string sfo_dir = rpcs3::utils::get_sfo_dir_from_game_path(dir);
const psf::registry psf = psf::load_object(fs::file(sfo_dir + "/PARAM.SFO"));
const std::string_view title_id = psf::get_string(psf, "TITLE_ID", "");
if (title_id.empty())
@ -579,7 +599,7 @@ void game_list_frame::Refresh(const bool from_drive, const bool scroll_after)
game.icon_path = sfo_dir + "/ICON0.PNG";
}
mutex_cat.lock();
m_mutex_cat.lock();
const QString serial = qstr(game.serial);
@ -612,7 +632,7 @@ void game_list_frame::Refresh(const bool from_drive, const bool scroll_after)
m_persistent_settings->SetPlaytime(serial, playtime);
}
serials.insert(serial);
m_serials.insert(serial);
if (!note.isEmpty())
{
@ -636,41 +656,59 @@ void game_list_frame::Refresh(const bool from_drive, const bool scroll_after)
}
else if (game.category == cat_unknown)
{
qt_cat = localized.category.unknown;
qt_cat = thread_localized.category.unknown;
}
else
{
qt_cat = localized.category.other;
qt_cat = thread_localized.category.other;
}
mutex_cat.unlock();
// Load ICON0.PNG
QPixmap icon;
if (game.icon_path.empty() || !icon.load(qstr(game.icon_path)))
{
game_list_log.warning("Could not load image from path %s", sstr(QDir(qstr(game.icon_path)).absolutePath()));
}
m_mutex_cat.unlock();
const auto compat = m_game_compat->GetCompatibility(game.serial);
const bool hasCustomConfig = fs::is_file(rpcs3::utils::get_custom_config_path(game.serial)) || fs::is_file(rpcs3::utils::get_custom_config_path(game.serial, true));
const bool hasCustomPadConfig = fs::is_file(rpcs3::utils::get_custom_input_config_path(game.serial));
const bool has_hover_gif = fs::is_file(game_icon_path + game.serial + "/hover.gif");
const QColor color = getGridCompatibilityColor(compat.color);
const QPixmap pxmap = PaintedPixmap(icon, hasCustomConfig, hasCustomPadConfig, color);
games.push(std::make_shared<gui_game_info>(gui_game_info{game, qt_cat, compat, icon, pxmap, hasCustomConfig, hasCustomPadConfig, has_hover_gif}));
m_games.push(std::make_shared<gui_game_info>(gui_game_info{game, qt_cat, compat, {}, {}, hasCustomConfig, hasCustomPadConfig, has_hover_gif, nullptr}));
}));
return;
}
});
for (auto&& g : games.pop_all())
// Fill Game List / Game Grid
if (m_is_list_layout)
{
const int scroll_position = m_game_list->verticalScrollBar()->value();
PopulateGameList();
SortGameList();
RepaintIcons();
if (scroll_after)
{
m_game_list->scrollTo(m_game_list->currentIndex(), QAbstractItemView::PositionAtCenter);
}
else
{
m_game_list->verticalScrollBar()->setValue(scroll_position);
}
}
else
{
RepaintIcons();
}
}
void game_list_frame::OnRefreshFinished()
{
for (auto&& g : m_games.pop_all())
{
m_game_data.push_back(g);
}
const Localized localized;
const std::string cat_unknown_localized = sstr(localized.category.unknown);
// Try to update the app version for disc games if there is a patch
for (const auto& entry : m_game_data)
{
@ -725,29 +763,31 @@ void game_list_frame::Refresh(const bool from_drive, const bool scroll_after)
});
// clean up hidden games list
m_hidden_list.intersect(serials);
m_hidden_list.intersect(m_serials);
m_gui_settings->SetValue(gui::gl_hidden_list, QStringList(m_hidden_list.values()));
m_serials.clear();
m_path_list.clear();
Refresh();
}
// Fill Game List / Game Grid
void game_list_frame::OnRepaintFinished()
{
if (m_is_list_layout)
{
const int scroll_position = m_game_list->verticalScrollBar()->value();
PopulateGameList();
SortGameList();
// Fixate vertical header and row height
m_game_list->verticalHeader()->setMinimumSectionSize(m_icon_size.height());
m_game_list->verticalHeader()->setMaximumSectionSize(m_icon_size.height());
if (scroll_after)
{
m_game_list->scrollTo(m_game_list->currentIndex(), QAbstractItemView::PositionAtCenter);
}
else
{
m_game_list->verticalScrollBar()->setValue(scroll_position);
}
// Resize the icon column
m_game_list->resizeColumnToContents(gui::column_icon);
// Shorten the last section to remove horizontal scrollbar if possible
m_game_list->resizeColumnToContents(gui::column_count - 1);
}
else
{
// The game grid needs to be recreated from scratch
int games_per_row = 0;
if (m_icon_size.width() > 0 && m_icon_size.height() > 0)
@ -758,7 +798,7 @@ void game_list_frame::Refresh(const bool from_drive, const bool scroll_after)
const int scroll_position = m_game_grid->verticalScrollBar()->value();
PopulateGameGrid(games_per_row, m_icon_size, m_icon_color);
connect(m_game_grid, &QTableWidget::customContextMenuRequested, this, &game_list_frame::ShowContextMenu);
connect(m_game_grid, &QTableWidget::itemSelectionChanged, this, &game_list_frame::itemSelectionChangedSlot);
connect(m_game_grid, &QTableWidget::itemSelectionChanged, this, &game_list_frame::ItemSelectionChangedSlot);
connect(m_game_grid, &QTableWidget::itemDoubleClicked, this, &game_list_frame::doubleClickedSlot);
m_central_widget->addWidget(m_game_grid);
m_central_widget->setCurrentWidget(m_game_grid);
@ -812,7 +852,7 @@ void game_list_frame::doubleClickedSlot(QTableWidgetItem *item)
Q_EMIT RequestBoot(game);
}
void game_list_frame::itemSelectionChangedSlot()
void game_list_frame::ItemSelectionChangedSlot()
{
game_info game = nullptr;
@ -1986,18 +2026,15 @@ void game_list_frame::RepaintIcons(const bool& from_settings)
}
}
QtConcurrent::blockingMap(m_game_data, [this](const game_info& game)
{
const QColor color = getGridCompatibilityColor(game->compat.color);
game->pxmap = PaintedPixmap(game->icon, game->hasCustomConfig, game->hasCustomPadConfig, color);
});
if (m_is_list_layout)
{
// We don't need a full Refresh just for the icons, so let's just trigger the icon callback of each icon.
for (int row = 0; row < m_game_list->rowCount(); ++row)
QPixmap placeholder(m_icon_size);
placeholder.fill(Qt::transparent);
for (auto& game : m_game_data)
{
if (const auto item = static_cast<custom_table_widget_item*>(m_game_list->item(row, gui::column_icon)))
game->pxmap = placeholder;
if (movie_item* item = game->item)
{
item->call_icon_func();
}
@ -2013,11 +2050,24 @@ void game_list_frame::RepaintIcons(const bool& from_settings)
// Shorten the last section to remove horizontal scrollbar if possible
m_game_list->resizeColumnToContents(gui::column_count - 1);
}
else
if (m_repaint_watcher.isRunning())
{
// The game grid needs to be recreated from scratch
Refresh();
m_repaint_watcher.cancel();
m_repaint_watcher.waitForFinished();
}
const std::function func = [this](const game_info& game) -> movie_item*
{
if (game->icon.isNull() && (game->info.icon_path.empty() || !game->icon.load(qstr(game->info.icon_path))))
{
game_list_log.warning("Could not load image from path %s", sstr(QDir(qstr(game->info.icon_path)).absolutePath()));
}
const QColor color = getGridCompatibilityColor(game->compat.color);
game->pxmap = PaintedPixmap(game->icon, game->hasCustomConfig, game->hasCustomPadConfig, color);
return game->item;
};
m_repaint_watcher.setFuture(QtConcurrent::mapped(m_game_data, func));
}
void game_list_frame::SetShowHidden(bool show)
@ -2153,6 +2203,7 @@ void game_list_frame::PopulateGameList()
// Icon
custom_table_widget_item* icon_item = new custom_table_widget_item;
game->item = icon_item;
icon_item->set_icon_func([this, icon_item, game](int)
{
@ -2320,6 +2371,7 @@ void game_list_frame::PopulateGameGrid(int maxCols, const QSize& image_size, con
movie_item* item = m_game_grid->addItem(app, title, (m_play_hover_movies && app->has_hover_gif) ? (game_icon_path % serial % "/hover.gif") : QStringLiteral(""), r, c);
ensure(item);
app->item = item;
item->setData(gui::game_role, QVariant::fromValue(app));
if (!notes.isEmpty())

View file

@ -3,12 +3,14 @@
#include "game_list.h"
#include "custom_dock_widget.h"
#include "gui_save.h"
#include "Utilities/lockless.h"
#include <QMainWindow>
#include <QToolBar>
#include <QStackedWidget>
#include <QSet>
#include <QTableWidgetItem>
#include <QFutureWatcher>
#include <memory>
@ -72,10 +74,12 @@ public Q_SLOTS:
void SetPlayHoverGifs(bool play);
private Q_SLOTS:
void OnRefreshFinished();
void OnRepaintFinished();
void OnColClicked(int col);
void ShowContextMenu(const QPoint &pos);
void doubleClickedSlot(QTableWidgetItem *item);
void itemSelectionChangedSlot();
void ItemSelectionChangedSlot();
Q_SIGNALS:
void GameListFrameClosed();
void NotifyGameSelection(const game_info& game);
@ -144,6 +148,12 @@ private:
std::shared_ptr<emu_settings> m_emu_settings;
std::shared_ptr<persistent_settings> m_persistent_settings;
QList<game_info> m_game_data;
std::vector<std::string> m_path_list;
QSet<QString> m_serials;
QMutex m_mutex_cat;
lf_queue<game_info> m_games;
QFutureWatcher<void> m_refresh_watcher;
QFutureWatcher<movie_item*> m_repaint_watcher;
QSet<QString> m_hidden_list;
bool m_show_hidden{false};