Qt: Lazy load game grid icons and optimize paint method for invisible items

This commit is contained in:
Megamouse 2023-04-22 12:15:19 +02:00
parent b47db88ded
commit 823b23f800
6 changed files with 103 additions and 105 deletions

View file

@ -149,10 +149,9 @@ game_list_frame::game_list_frame(std::shared_ptr<gui_settings> gui_settings, std
m_serials.clear(); m_serials.clear();
m_games.pop_all(); m_games.pop_all();
}); });
connect(&m_repaint_watcher, &QFutureWatcher<movie_item*>::finished, this, &game_list_frame::OnRepaintFinished);
connect(this, &game_list_frame::IconReady, this, [this](movie_item* item) connect(this, &game_list_frame::IconReady, this, [this](movie_item* item)
{ {
if (!m_is_list_layout || !item) return; if (!item) return;
item->call_icon_func(); item->call_icon_func();
}); });
connect(this, &game_list_frame::SizeOnDiskReady, this, [this](const game_info& game) connect(this, &game_list_frame::SizeOnDiskReady, this, [this](const game_info& game)
@ -882,29 +881,6 @@ void game_list_frame::OnRefreshFinished()
} }
} }
void game_list_frame::OnRepaintFinished()
{
if (!m_is_list_layout)
{
// 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)
{
games_per_row = width() / (m_icon_size.width() + m_icon_size.width() * m_game_grid->getMarginFactor() * 2);
}
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::itemDoubleClicked, this, &game_list_frame::doubleClickedSlot);
m_central_widget->addWidget(m_game_grid);
m_central_widget->setCurrentWidget(m_game_grid);
m_game_grid->verticalScrollBar()->setValue(scroll_position);
}
}
void game_list_frame::OnCompatFinished() void game_list_frame::OnCompatFinished()
{ {
for (const auto& game : m_game_data) for (const auto& game : m_game_data)
@ -2409,7 +2385,7 @@ void game_list_frame::RepaintIcons(const bool& from_settings)
QPixmap placeholder(m_icon_size); QPixmap placeholder(m_icon_size);
placeholder.fill(Qt::transparent); placeholder.fill(Qt::transparent);
for (auto& game : m_game_data) for (game_info& game : m_game_data)
{ {
game->pxmap = placeholder; game->pxmap = placeholder;
@ -2417,46 +2393,7 @@ void game_list_frame::RepaintIcons(const bool& from_settings)
{ {
item->set_icon_load_func([this, game, cancel = item->icon_loading_aborted()]() item->set_icon_load_func([this, game, cancel = item->icon_loading_aborted()]()
{ {
if (cancel && cancel->load()) IconLoadFunction(game, cancel);
{
return;
}
static std::unordered_set<std::string> warn_once_list;
static shared_mutex s_mtx;
if (game->icon.isNull() && (game->info.icon_path.empty() || !game->icon.load(qstr(game->info.icon_path))))
{
if (game_list_log.warning)
{
bool logged = false;
{
std::lock_guard lock(s_mtx);
logged = !warn_once_list.emplace(game->info.icon_path).second;
}
if (!logged)
{
game_list_log.warning("Could not load image from path %s", sstr(QDir(qstr(game->info.icon_path)).absolutePath()));
}
}
}
if (!game->item || (cancel && cancel->load()))
{
return;
}
const QColor color = getGridCompatibilityColor(game->compat.color);
{
std::lock_guard lock(game->item->pixmap_mutex);
game->pxmap = PaintedPixmap(game->icon, game->hasCustomConfig, game->hasCustomPadConfig, color);
}
if (!cancel || !cancel->load())
{
Q_EMIT IconReady(game->item);
}
}); });
item->call_icon_func(); item->call_icon_func();
} }
@ -2471,37 +2408,26 @@ void game_list_frame::RepaintIcons(const bool& from_settings)
// Shorten the last section to remove horizontal scrollbar if possible // Shorten the last section to remove horizontal scrollbar if possible
m_game_list->resizeColumnToContents(gui::column_count - 1); m_game_list->resizeColumnToContents(gui::column_count - 1);
}
else
{
// The game grid needs to be recreated from scratch
int games_per_row = 0;
return; if (m_icon_size.width() > 0 && m_icon_size.height() > 0)
{
games_per_row = width() / (m_icon_size.width() + m_icon_size.width() * m_game_grid->getMarginFactor() * 2);
} }
const std::function func = [this](const game_info& game) -> movie_item* const int scroll_position = m_game_grid->verticalScrollBar()->value();
{ PopulateGameGrid(games_per_row, m_icon_size, m_icon_color);
static std::unordered_set<std::string> warn_once_list; connect(m_game_grid, &QTableWidget::customContextMenuRequested, this, &game_list_frame::ShowContextMenu);
static shared_mutex s_mtx; connect(m_game_grid, &QTableWidget::itemSelectionChanged, this, &game_list_frame::ItemSelectionChangedSlot);
connect(m_game_grid, &QTableWidget::itemDoubleClicked, this, &game_list_frame::doubleClickedSlot);
if (game->icon.isNull() && (game->info.icon_path.empty() || !game->icon.load(qstr(game->info.icon_path)))) m_central_widget->addWidget(m_game_grid);
{ m_central_widget->setCurrentWidget(m_game_grid);
if (game_list_log.warning) m_game_grid->verticalScrollBar()->setValue(scroll_position);
{
bool logged = false;
{
std::lock_guard lock(s_mtx);
logged = !warn_once_list.emplace(game->info.icon_path).second;
} }
if (!logged)
{
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) void game_list_frame::SetShowHidden(bool show)
@ -2647,7 +2573,10 @@ void game_list_frame::PopulateGameList()
icon_item->set_icon_func([this, icon_item, game](int) icon_item->set_icon_func([this, icon_item, game](int)
{ {
ensure(icon_item && game); if (!icon_item || !game)
{
return;
}
if (std::shared_ptr<QMovie> movie = icon_item->movie(); movie && icon_item->get_active()) if (std::shared_ptr<QMovie> movie = icon_item->movie(); movie && icon_item->get_active())
{ {
@ -2821,7 +2750,7 @@ void game_list_frame::PopulateGameGrid(int maxCols, const QSize& image_size, con
const QString game_icon_path = m_play_hover_movies ? qstr(fs::get_config_dir() + "/Icons/game_icons/") : ""; const QString game_icon_path = m_play_hover_movies ? qstr(fs::get_config_dir() + "/Icons/game_icons/") : "";
for (const auto& app : matching_apps) for (const game_info& app : matching_apps)
{ {
const QString serial = qstr(app->info.serial); const QString serial = qstr(app->info.serial);
const QString title = m_titles.value(serial, qstr(app->info.name)); const QString title = m_titles.value(serial, qstr(app->info.name));
@ -2831,6 +2760,10 @@ void game_list_frame::PopulateGameGrid(int maxCols, const QSize& image_size, con
ensure(item); ensure(item);
app->item = item; app->item = item;
item->setData(gui::game_role, QVariant::fromValue(app)); item->setData(gui::game_role, QVariant::fromValue(app));
item->set_icon_load_func([this, app, cancel = item->icon_loading_aborted()]()
{
IconLoadFunction(app, cancel);
});
if (!notes.isEmpty()) if (!notes.isEmpty())
{ {
@ -3032,10 +2965,52 @@ std::string game_list_frame::GetGameVersion(const game_info& game)
return game->info.app_ver; return game->info.app_ver;
} }
void game_list_frame::IconLoadFunction(game_info game, std::shared_ptr<atomic_t<bool>> cancel)
{
if (cancel && cancel->load())
{
return;
}
static std::unordered_set<std::string> warn_once_list;
static shared_mutex s_mtx;
if (game->icon.isNull() && (game->info.icon_path.empty() || !game->icon.load(qstr(game->info.icon_path))))
{
if (game_list_log.warning)
{
bool logged = false;
{
std::lock_guard lock(s_mtx);
logged = !warn_once_list.emplace(game->info.icon_path).second;
}
if (!logged)
{
game_list_log.warning("Could not load image from path %s", sstr(QDir(qstr(game->info.icon_path)).absolutePath()));
}
}
}
if (!game->item || (cancel && cancel->load()))
{
return;
}
const QColor color = getGridCompatibilityColor(game->compat.color);
{
std::lock_guard lock(game->item->pixmap_mutex);
game->pxmap = PaintedPixmap(game->icon, game->hasCustomConfig, game->hasCustomPadConfig, color);
}
if (!cancel || !cancel->load())
{
Q_EMIT IconReady(game->item);
}
}
void game_list_frame::WaitAndAbortRepaintThreads() void game_list_frame::WaitAndAbortRepaintThreads()
{ {
gui::utils::stop_future_watcher(m_repaint_watcher, true);
for (const game_info& game : m_game_data) for (const game_info& game : m_game_data)
{ {
if (game && game->item) if (game && game->item)

View file

@ -80,7 +80,6 @@ public Q_SLOTS:
private Q_SLOTS: private Q_SLOTS:
void OnRefreshFinished(); void OnRefreshFinished();
void OnRepaintFinished();
void OnCompatFinished(); void OnCompatFinished();
void OnColClicked(int col); void OnColClicked(int col);
void ShowContextMenu(const QPoint &pos); void ShowContextMenu(const QPoint &pos);
@ -102,6 +101,7 @@ protected:
private: private:
QPixmap PaintedPixmap(const QPixmap& icon, bool paint_config_icon = false, bool paint_pad_config_icon = false, const QColor& color = QColor()) const; QPixmap PaintedPixmap(const QPixmap& icon, bool paint_config_icon = false, bool paint_pad_config_icon = false, const QColor& color = QColor()) const;
QColor getGridCompatibilityColor(const QString& string) const; QColor getGridCompatibilityColor(const QString& string) const;
void IconLoadFunction(game_info game, std::shared_ptr<atomic_t<bool>> cancel);
/** Sets the custom config icon. Only call this for list title items. */ /** Sets the custom config icon. Only call this for list title items. */
void SetCustomConfigIcon(QTableWidgetItem* title_item, const game_info& game); void SetCustomConfigIcon(QTableWidgetItem* title_item, const game_info& game);
@ -174,7 +174,6 @@ private:
QMutex m_games_mutex; QMutex m_games_mutex;
lf_queue<game_info> m_games; lf_queue<game_info> m_games;
QFutureWatcher<void> m_refresh_watcher; QFutureWatcher<void> m_refresh_watcher;
QFutureWatcher<movie_item*> m_repaint_watcher;
QSet<QString> m_hidden_list; QSet<QString> m_hidden_list;
bool m_show_hidden{false}; bool m_show_hidden{false};

View file

@ -66,7 +66,10 @@ movie_item* game_list_grid::addItem(const game_info& app, const QString& name, c
item->set_icon_func([this, app, item](int) item->set_icon_func([this, app, item](int)
{ {
ensure(item); if (!item)
{
return;
}
const qreal device_pixel_ratio = devicePixelRatioF(); const qreal device_pixel_ratio = devicePixelRatioF();

View file

@ -1,4 +1,7 @@
#include "game_list_grid_delegate.h" #include "game_list_grid_delegate.h"
#include "movie_item.h"
#include <QTableWidget>
game_list_grid_delegate::game_list_grid_delegate(const QSize& size, const qreal& margin_factor, const qreal& text_factor, QObject *parent) game_list_grid_delegate::game_list_grid_delegate(const QSize& size, const qreal& margin_factor, const qreal& text_factor, QObject *parent)
: QStyledItemDelegate(parent), m_size(size), m_margin_factor(margin_factor), m_text_factor(text_factor) : QStyledItemDelegate(parent), m_size(size), m_margin_factor(margin_factor), m_text_factor(text_factor)
@ -23,13 +26,31 @@ void game_list_grid_delegate::paint(QPainter* painter, const QStyleOptionViewIte
painter->setRenderHints(QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform); painter->setRenderHints(QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform);
painter->eraseRect(r); painter->eraseRect(r);
// Paint from our stylesheet
QStyledItemDelegate::paint(painter, option, index);
// Check if the item is visible
if (const QTableWidget* table = static_cast<const QTableWidget*>(parent()))
{
if (movie_item* item = static_cast<movie_item*>(table->item(index.row(), index.column())))
{
if (!table->visibleRegion().intersects(table->visualItemRect(item)))
{
// Skip all further actions if the item is not visible
return;
}
if (!item->icon_loading())
{
item->call_icon_load_func();
}
}
}
// Get title and image // Get title and image
const QPixmap image = qvariant_cast<QPixmap>(index.data(Qt::DecorationRole)); const QPixmap image = qvariant_cast<QPixmap>(index.data(Qt::DecorationRole));
const QString title = index.data(Qt::DisplayRole).toString(); const QString title = index.data(Qt::DisplayRole).toString();
// Paint from our stylesheet
QStyledItemDelegate::paint(painter, option, index);
// image // image
if (image.isNull() == false) if (image.isNull() == false)
{ {

View file

@ -129,7 +129,7 @@ void movie_item::set_size_calc_func(const size_calc_callback_t& func)
void movie_item::wait_for_icon_loading(bool abort) void movie_item::wait_for_icon_loading(bool abort)
{ {
if (m_icon_load_thread) if (m_icon_load_thread && m_icon_load_thread->isRunning())
{ {
*m_icon_loading_aborted = abort; *m_icon_loading_aborted = abort;
m_icon_load_thread->wait(); m_icon_load_thread->wait();
@ -139,7 +139,7 @@ void movie_item::wait_for_icon_loading(bool abort)
void movie_item::wait_for_size_on_disk_loading(bool abort) void movie_item::wait_for_size_on_disk_loading(bool abort)
{ {
if (m_size_calc_thread) if (m_size_calc_thread && m_size_calc_thread->isRunning())
{ {
*m_size_on_disk_loading_aborted = abort; *m_size_on_disk_loading_aborted = abort;
m_size_calc_thread->wait(); m_size_calc_thread->wait();

View file

@ -61,12 +61,12 @@ public:
return m_size_on_disk_loading; return m_size_on_disk_loading;
} }
std::shared_ptr<atomic_t<bool>> icon_loading_aborted() const [[nodiscard]] std::shared_ptr<atomic_t<bool>> icon_loading_aborted() const
{ {
return m_icon_loading_aborted; return m_icon_loading_aborted;
} }
std::shared_ptr<atomic_t<bool>> size_on_disk_loading_aborted() const [[nodiscard]] std::shared_ptr<atomic_t<bool>> size_on_disk_loading_aborted() const
{ {
return m_size_on_disk_loading_aborted; return m_size_on_disk_loading_aborted;
} }