Qt: Improve game grid layout calculations

Use grid size instead of icon size/spacing.

Fixes the number of columns changing when different items are visible.

Also restores the old behaviour of dynamic scroll bar visibility.
This commit is contained in:
Stenzek 2025-06-05 17:23:14 +10:00
parent cb751b0990
commit 22089e9b75
No known key found for this signature in database
2 changed files with 158 additions and 128 deletions

View File

@ -15,6 +15,7 @@
#include "common/assert.h" #include "common/assert.h"
#include "common/file_system.h" #include "common/file_system.h"
#include "common/log.h"
#include "common/path.h" #include "common/path.h"
#include "common/string_util.h" #include "common/string_util.h"
@ -32,6 +33,8 @@
#include <QtWidgets/QStyledItemDelegate> #include <QtWidgets/QStyledItemDelegate>
#include <algorithm> #include <algorithm>
LOG_CHANNEL(GameList);
static constexpr float MIN_SCALE = 0.1f; static constexpr float MIN_SCALE = 0.1f;
static constexpr float MAX_SCALE = 2.0f; static constexpr float MAX_SCALE = 2.0f;
@ -47,10 +50,10 @@ static constexpr std::array<const char*, GameListModel::Column_Count> s_column_n
{"Icon", "Serial", "Title", "File Title", "Developer", "Publisher", "Genre", "Year", "Players", "Time Played", {"Icon", "Serial", "Title", "File Title", "Developer", "Publisher", "Genre", "Year", "Players", "Time Played",
"Last Played", "Size", "File Size", "Region", "Achievements", "Compatibility", "Cover"}}; "Last Played", "Size", "File Size", "Region", "Achievements", "Compatibility", "Cover"}};
static constexpr int COVER_ART_WIDTH = 512; static constexpr int COVER_ART_SIZE = 512;
static constexpr int COVER_ART_HEIGHT = 512;
static constexpr int COVER_ART_SPACING = 32; static constexpr int COVER_ART_SPACING = 32;
static constexpr int MIN_COVER_CACHE_SIZE = 256; static constexpr int MIN_COVER_CACHE_SIZE = 256;
static constexpr int MIN_COVER_CACHE_ROW_BUFFER = 4;
static void resizeAndPadImage(QImage* image, int expected_width, int expected_height, bool fill_with_top_left) static void resizeAndPadImage(QImage* image, int expected_width, int expected_height, bool fill_with_top_left)
{ {
@ -159,11 +162,11 @@ void GameListModel::setCoverScale(float scale)
if (loading_image.load(QStringLiteral("%1/images/placeholder.png").arg(QtHost::GetResourcesBasePath()))) if (loading_image.load(QStringLiteral("%1/images/placeholder.png").arg(QtHost::GetResourcesBasePath())))
{ {
loading_image.setDevicePixelRatio(dpr); loading_image.setDevicePixelRatio(dpr);
resizeAndPadImage(&loading_image, getCoverArtWidth(), getCoverArtHeight(), false); resizeAndPadImage(&loading_image, getCoverArtSize(), getCoverArtSize(), false);
} }
else else
{ {
loading_image = QImage(getCoverArtWidth(), getCoverArtHeight(), QImage::Format_RGB32); loading_image = QImage(getCoverArtSize(), getCoverArtSize(), QImage::Format_RGB32);
loading_image.setDevicePixelRatio(dpr); loading_image.setDevicePixelRatio(dpr);
loading_image.fill(QColor(0, 0, 0, 0)); loading_image.fill(QColor(0, 0, 0, 0));
} }
@ -173,11 +176,11 @@ void GameListModel::setCoverScale(float scale)
if (m_placeholder_image.load(QStringLiteral("%1/images/cover-placeholder.png").arg(QtHost::GetResourcesBasePath()))) if (m_placeholder_image.load(QStringLiteral("%1/images/cover-placeholder.png").arg(QtHost::GetResourcesBasePath())))
{ {
m_placeholder_image.setDevicePixelRatio(dpr); m_placeholder_image.setDevicePixelRatio(dpr);
resizeAndPadImage(&m_placeholder_image, getCoverArtWidth(), getCoverArtHeight(), false); resizeAndPadImage(&m_placeholder_image, getCoverArtSize(), getCoverArtSize(), false);
} }
else else
{ {
m_placeholder_image = QImage(getCoverArtWidth(), getCoverArtHeight(), QImage::Format_RGB32); m_placeholder_image = QImage(getCoverArtSize(), getCoverArtSize(), QImage::Format_RGB32);
m_placeholder_image.setDevicePixelRatio(dpr); m_placeholder_image.setDevicePixelRatio(dpr);
m_placeholder_image.fill(QColor(0, 0, 0, 0)); m_placeholder_image.fill(QColor(0, 0, 0, 0));
} }
@ -191,14 +194,13 @@ void GameListModel::refreshCovers()
refresh(); refresh();
} }
void GameListModel::updateCacheSize(int width, int height) void GameListModel::updateCacheSize(int num_rows, int num_columns)
{ {
// Add additional buffer zone to the rows, since Qt will grab them early when scrolling.
const int num_items = (num_rows + MIN_COVER_CACHE_ROW_BUFFER) * num_columns;
// This is a bit conversative, since it doesn't consider padding, but better to be over than under. // This is a bit conversative, since it doesn't consider padding, but better to be over than under.
const int cover_width = getCoverArtWidth(); m_cover_pixmap_cache.SetMaxCapacity(static_cast<int>(std::max(num_items, MIN_COVER_CACHE_SIZE)));
const int cover_height = getCoverArtHeight();
const int num_columns = ((width + (cover_width - 1)) / cover_width);
const int num_rows = ((height + (cover_height - 1)) / cover_height);
m_cover_pixmap_cache.SetMaxCapacity(static_cast<int>(std::max(num_columns * num_rows, MIN_COVER_CACHE_SIZE)));
} }
void GameListModel::reloadThemeSpecificImages() void GameListModel::reloadThemeSpecificImages()
@ -210,8 +212,8 @@ void GameListModel::reloadThemeSpecificImages()
void GameListModel::loadOrGenerateCover(const GameList::Entry* ge) void GameListModel::loadOrGenerateCover(const GameList::Entry* ge)
{ {
QtAsyncTask::create(this, [path = ge->path, serial = ge->serial, title = ge->title, QtAsyncTask::create(this, [path = ge->path, serial = ge->serial, title = ge->title,
placeholder_image = m_placeholder_image, list = this, width = getCoverArtWidth(), placeholder_image = m_placeholder_image, list = this, width = getCoverArtSize(),
height = getCoverArtHeight(), scale = m_cover_scale, height = getCoverArtSize(), scale = m_cover_scale,
dpr = qApp->devicePixelRatio()]() mutable { dpr = qApp->devicePixelRatio()]() mutable {
QImage image; QImage image;
loadOrGenerateCover(image, placeholder_image, width, height, scale, dpr, path, serial, title); loadOrGenerateCover(image, placeholder_image, width, height, scale, dpr, path, serial, title);
@ -433,14 +435,9 @@ void GameListModel::fixIconPixmapSize(QPixmap& pm)
pm = pm.scaled(new_width, new_height); pm = pm.scaled(new_width, new_height);
} }
int GameListModel::getCoverArtWidth() const int GameListModel::getCoverArtSize() const
{ {
return std::max(static_cast<int>(static_cast<float>(COVER_ART_WIDTH) * m_cover_scale), 1); return std::max(static_cast<int>(static_cast<float>(COVER_ART_SIZE) * m_cover_scale), 1);
}
int GameListModel::getCoverArtHeight() const
{
return std::max(static_cast<int>(static_cast<float>(COVER_ART_HEIGHT) * m_cover_scale), 1);
} }
int GameListModel::getCoverArtSpacing() const int GameListModel::getCoverArtSpacing() const
@ -1114,6 +1111,25 @@ private:
GameListSortModel* m_sort_model; GameListSortModel* m_sort_model;
}; };
class GameListGridDelegate : public QStyledItemDelegate
{
public:
GameListGridDelegate(bool show_titles) : m_show_titles(show_titles) {}
protected:
void initStyleOption(QStyleOptionViewItem* option, const QModelIndex& index) const override
{
QStyledItemDelegate::initStyleOption(option, index);
if (!m_show_titles)
option->features &= ~QStyleOptionViewItem::HasDisplay;
// else
// option->features |= QStyleOptionViewItem::WrapText;
}
private:
bool m_show_titles = false;
};
} // namespace } // namespace
GameListWidget::GameListWidget(QWidget* parent /* = nullptr */) : QWidget(parent) GameListWidget::GameListWidget(QWidget* parent /* = nullptr */) : QWidget(parent)
@ -1129,7 +1145,6 @@ void GameListWidget::initialize()
const bool merge_disc_sets = Host::GetBaseBoolSettingValue("UI", "GameListMergeDiscSets", true); const bool merge_disc_sets = Host::GetBaseBoolSettingValue("UI", "GameListMergeDiscSets", true);
const bool show_game_icons = Host::GetBaseBoolSettingValue("UI", "GameListShowGameIcons", true); const bool show_game_icons = Host::GetBaseBoolSettingValue("UI", "GameListShowGameIcons", true);
m_model = new GameListModel(cover_scale, show_cover_titles, show_game_icons, this); m_model = new GameListModel(cover_scale, show_cover_titles, show_game_icons, this);
m_model->updateCacheSize(width(), height());
m_sort_model = new GameListSortModel(m_model); m_sort_model = new GameListSortModel(m_model);
m_sort_model->setSourceModel(m_model); m_sort_model->setSourceModel(m_model);
@ -1165,65 +1180,64 @@ void GameListWidget::initialize()
connect(m_ui.searchText, &QLineEdit::returnPressed, this, &GameListWidget::onSearchReturnPressed); connect(m_ui.searchText, &QLineEdit::returnPressed, this, &GameListWidget::onSearchReturnPressed);
GameListCenterIconStyleDelegate* center_icon_delegate = new GameListCenterIconStyleDelegate(this); GameListCenterIconStyleDelegate* center_icon_delegate = new GameListCenterIconStyleDelegate(this);
m_table_view = new QTableView(m_ui.stack); m_list_view = new QTableView(m_ui.stack);
m_table_view->setModel(m_sort_model); m_list_view->setModel(m_sort_model);
m_table_view->setSortingEnabled(true); m_list_view->setSortingEnabled(true);
m_table_view->setSelectionMode(QAbstractItemView::SingleSelection); m_list_view->setSelectionMode(QAbstractItemView::SingleSelection);
m_table_view->setSelectionBehavior(QAbstractItemView::SelectRows); m_list_view->setSelectionBehavior(QAbstractItemView::SelectRows);
m_table_view->setContextMenuPolicy(Qt::CustomContextMenu); m_list_view->setContextMenuPolicy(Qt::CustomContextMenu);
m_table_view->setAlternatingRowColors(true); m_list_view->setAlternatingRowColors(true);
m_table_view->setShowGrid(false); m_list_view->setShowGrid(false);
m_table_view->setCurrentIndex({}); m_list_view->setCurrentIndex({});
m_table_view->horizontalHeader()->setHighlightSections(false); m_list_view->horizontalHeader()->setHighlightSections(false);
m_table_view->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu); m_list_view->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu);
m_table_view->verticalHeader()->hide(); m_list_view->verticalHeader()->hide();
m_table_view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); m_list_view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
m_table_view->setVerticalScrollMode(QAbstractItemView::ScrollMode::ScrollPerPixel); m_list_view->setVerticalScrollMode(QAbstractItemView::ScrollMode::ScrollPerPixel);
m_table_view->setItemDelegateForColumn(GameListModel::Column_Icon, center_icon_delegate); m_list_view->setItemDelegateForColumn(GameListModel::Column_Icon, center_icon_delegate);
m_table_view->setItemDelegateForColumn(GameListModel::Column_Region, center_icon_delegate); m_list_view->setItemDelegateForColumn(GameListModel::Column_Region, center_icon_delegate);
m_table_view->setItemDelegateForColumn(GameListModel::Column_Achievements, m_list_view->setItemDelegateForColumn(GameListModel::Column_Achievements,
new GameListAchievementsStyleDelegate(this, m_model, m_sort_model)); new GameListAchievementsStyleDelegate(this, m_model, m_sort_model));
loadTableViewColumnVisibilitySettings(); loadTableViewColumnVisibilitySettings();
loadTableViewColumnSortSettings(); loadTableViewColumnSortSettings();
connect(m_table_view->selectionModel(), &QItemSelectionModel::currentChanged, this, connect(m_list_view->selectionModel(), &QItemSelectionModel::currentChanged, this,
&GameListWidget::onSelectionModelCurrentChanged); &GameListWidget::onSelectionModelCurrentChanged);
connect(m_table_view, &QTableView::activated, this, &GameListWidget::onTableViewItemActivated); connect(m_list_view, &QTableView::activated, this, &GameListWidget::onTableViewItemActivated);
connect(m_table_view, &QTableView::customContextMenuRequested, this, connect(m_list_view, &QTableView::customContextMenuRequested, this, &GameListWidget::onTableViewContextMenuRequested);
&GameListWidget::onTableViewContextMenuRequested); connect(m_list_view->horizontalHeader(), &QHeaderView::customContextMenuRequested, this,
connect(m_table_view->horizontalHeader(), &QHeaderView::customContextMenuRequested, this,
&GameListWidget::onTableViewHeaderContextMenuRequested); &GameListWidget::onTableViewHeaderContextMenuRequested);
connect(m_table_view->horizontalHeader(), &QHeaderView::sortIndicatorChanged, this, connect(m_list_view->horizontalHeader(), &QHeaderView::sortIndicatorChanged, this,
&GameListWidget::onTableViewHeaderSortIndicatorChanged); &GameListWidget::onTableViewHeaderSortIndicatorChanged);
m_ui.stack->insertWidget(0, m_table_view); m_ui.stack->insertWidget(0, m_list_view);
m_list_view = new GameListGridListView(m_ui.stack); m_grid_view = new GameListGridListView(m_model, m_ui.stack);
m_list_view->setModel(m_sort_model); m_grid_view->setModel(m_sort_model);
m_list_view->setModelColumn(GameListModel::Column_Cover); m_grid_view->setModelColumn(GameListModel::Column_Cover);
m_list_view->setSelectionMode(QAbstractItemView::SingleSelection); m_grid_view->setSelectionMode(QAbstractItemView::SingleSelection);
m_list_view->setViewMode(QListView::IconMode); m_grid_view->setViewMode(QListView::IconMode);
m_list_view->setResizeMode(QListView::Adjust); m_grid_view->setResizeMode(QListView::Adjust);
m_list_view->setUniformItemSizes(true); m_grid_view->setUniformItemSizes(true);
m_list_view->setItemAlignment(Qt::AlignHCenter); m_grid_view->setItemAlignment(Qt::AlignHCenter);
m_list_view->setContextMenuPolicy(Qt::CustomContextMenu); m_grid_view->setContextMenuPolicy(Qt::CustomContextMenu);
m_list_view->setFrameStyle(QFrame::NoFrame); m_grid_view->setFrameStyle(QFrame::NoFrame);
m_list_view->setVerticalScrollMode(QAbstractItemView::ScrollMode::ScrollPerPixel); m_grid_view->setVerticalScrollMode(QAbstractItemView::ScrollMode::ScrollPerPixel);
m_list_view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); m_grid_view->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
m_list_view->verticalScrollBar()->setSingleStep(15); m_grid_view->verticalScrollBar()->setSingleStep(15);
onCoverScaleChanged(); onCoverScaleChanged();
connect(m_list_view->selectionModel(), &QItemSelectionModel::currentChanged, this, connect(m_grid_view->selectionModel(), &QItemSelectionModel::currentChanged, this,
&GameListWidget::onSelectionModelCurrentChanged); &GameListWidget::onSelectionModelCurrentChanged);
connect(m_list_view, &GameListGridListView::zoomIn, this, &GameListWidget::gridZoomIn); connect(m_grid_view, &GameListGridListView::zoomIn, this, &GameListWidget::gridZoomIn);
connect(m_list_view, &GameListGridListView::zoomOut, this, &GameListWidget::gridZoomOut); connect(m_grid_view, &GameListGridListView::zoomOut, this, &GameListWidget::gridZoomOut);
connect(m_list_view, &QListView::activated, this, &GameListWidget::onListViewItemActivated); connect(m_grid_view, &QListView::activated, this, &GameListWidget::onListViewItemActivated);
connect(m_list_view, &QListView::customContextMenuRequested, this, &GameListWidget::onListViewContextMenuRequested); connect(m_grid_view, &QListView::customContextMenuRequested, this, &GameListWidget::onListViewContextMenuRequested);
connect(m_model, &GameListModel::coverScaleChanged, this, &GameListWidget::onCoverScaleChanged); connect(m_model, &GameListModel::coverScaleChanged, this, &GameListWidget::onCoverScaleChanged);
m_ui.stack->insertWidget(1, m_list_view); m_ui.stack->insertWidget(1, m_grid_view);
m_empty_widget = new QWidget(m_ui.stack); m_empty_widget = new QWidget(m_ui.stack);
m_empty_ui.setupUi(m_empty_widget); m_empty_ui.setupUi(m_empty_widget);
@ -1237,7 +1251,7 @@ void GameListWidget::initialize()
m_ui.stack->setCurrentIndex(1); m_ui.stack->setCurrentIndex(1);
else else
m_ui.stack->setCurrentIndex(0); m_ui.stack->setCurrentIndex(0);
setFocusProxy(grid_view ? static_cast<QWidget*>(m_list_view) : static_cast<QWidget*>(m_table_view)); setFocusProxy(grid_view ? static_cast<QWidget*>(m_grid_view) : static_cast<QWidget*>(m_list_view));
updateToolbar(); updateToolbar();
resizeTableViewColumnsToFit(); resizeTableViewColumnsToFit();
@ -1321,7 +1335,7 @@ void GameListWidget::updateBackground(bool reload_image)
if (m_background_image.isNull()) if (m_background_image.isNull())
{ {
m_ui.stack->setPalette(palette()); m_ui.stack->setPalette(palette());
m_table_view->setAlternatingRowColors(true); m_list_view->setAlternatingRowColors(true);
return; return;
} }
@ -1336,7 +1350,7 @@ void GameListWidget::updateBackground(bool reload_image)
QPalette new_palette(m_ui.stack->palette()); QPalette new_palette(m_ui.stack->palette());
new_palette.setBrush(QPalette::Base, QPixmap::fromImage(image)); new_palette.setBrush(QPalette::Base, QPixmap::fromImage(image));
m_ui.stack->setPalette(new_palette); m_ui.stack->setPalette(new_palette);
m_table_view->setAlternatingRowColors(false); m_list_view->setAlternatingRowColors(false);
}; };
}); });
} }
@ -1353,7 +1367,7 @@ void GameListWidget::onRefreshProgress(const QString& status, int current, int t
{ {
const bool grid_view = Host::GetBaseBoolSettingValue("UI", "GameListGridView", false); const bool grid_view = Host::GetBaseBoolSettingValue("UI", "GameListGridView", false);
m_ui.stack->setCurrentIndex(grid_view ? 1 : 0); m_ui.stack->setCurrentIndex(grid_view ? 1 : 0);
setFocusProxy(grid_view ? static_cast<QWidget*>(m_list_view) : static_cast<QWidget*>(m_table_view)); setFocusProxy(grid_view ? static_cast<QWidget*>(m_grid_view) : static_cast<QWidget*>(m_list_view));
} }
if (!m_model->hasTakenGameList() || time >= SHORT_REFRESH_TIME) if (!m_model->hasTakenGameList() || time >= SHORT_REFRESH_TIME)
@ -1408,7 +1422,7 @@ void GameListWidget::onTableViewItemActivated(const QModelIndex& index)
void GameListWidget::onTableViewContextMenuRequested(const QPoint& point) void GameListWidget::onTableViewContextMenuRequested(const QPoint& point)
{ {
emit entryContextMenuRequested(m_table_view->mapToGlobal(point)); emit entryContextMenuRequested(m_list_view->mapToGlobal(point));
} }
void GameListWidget::onListViewItemActivated(const QModelIndex& index) void GameListWidget::onListViewItemActivated(const QModelIndex& index)
@ -1422,7 +1436,7 @@ void GameListWidget::onListViewItemActivated(const QModelIndex& index)
void GameListWidget::onListViewContextMenuRequested(const QPoint& point) void GameListWidget::onListViewContextMenuRequested(const QPoint& point)
{ {
emit entryContextMenuRequested(m_list_view->mapToGlobal(point)); emit entryContextMenuRequested(m_grid_view->mapToGlobal(point));
} }
void GameListWidget::onTableViewHeaderContextMenuRequested(const QPoint& point) void GameListWidget::onTableViewHeaderContextMenuRequested(const QPoint& point)
@ -1436,15 +1450,15 @@ void GameListWidget::onTableViewHeaderContextMenuRequested(const QPoint& point)
QAction* action = menu.addAction(m_model->getColumnDisplayName(column)); QAction* action = menu.addAction(m_model->getColumnDisplayName(column));
action->setCheckable(true); action->setCheckable(true);
action->setChecked(!m_table_view->isColumnHidden(column)); action->setChecked(!m_list_view->isColumnHidden(column));
connect(action, &QAction::toggled, [this, column](bool enabled) { connect(action, &QAction::toggled, [this, column](bool enabled) {
m_table_view->setColumnHidden(column, !enabled); m_list_view->setColumnHidden(column, !enabled);
saveTableViewColumnVisibilitySettings(column); saveTableViewColumnVisibilitySettings(column);
resizeTableViewColumnsToFit(); resizeTableViewColumnsToFit();
}); });
} }
menu.exec(m_table_view->mapToGlobal(point)); menu.exec(m_list_view->mapToGlobal(point));
} }
void GameListWidget::onTableViewHeaderSortIndicatorChanged(int, Qt::SortOrder) void GameListWidget::onTableViewHeaderSortIndicatorChanged(int, Qt::SortOrder)
@ -1454,16 +1468,11 @@ void GameListWidget::onTableViewHeaderSortIndicatorChanged(int, Qt::SortOrder)
void GameListWidget::onCoverScaleChanged() void GameListWidget::onCoverScaleChanged()
{ {
m_model->updateCacheSize(width(), height());
m_list_view->setSpacing(m_model->getCoverArtSpacing());
m_list_view->setIconSize(QSize(m_model->getCoverArtWidth(), m_model->getCoverArtHeight()));
QFont font; QFont font;
font.setPointSizeF(20.0f * m_model->getCoverScale()); font.setPointSizeF(20.0f * m_model->getCoverScale());
m_list_view->setFont(font); m_grid_view->setFont(font);
m_list_view->updateLayout(); m_grid_view->updateLayout();
} }
void GameListWidget::listZoom(float delta) void GameListWidget::listZoom(float delta)
@ -1518,7 +1527,7 @@ void GameListWidget::onSearchReturnPressed()
return; return;
QAbstractItemView* const target = QAbstractItemView* const target =
isShowingGameGrid() ? static_cast<QAbstractItemView*>(m_list_view) : static_cast<QAbstractItemView*>(m_table_view); isShowingGameGrid() ? static_cast<QAbstractItemView*>(m_grid_view) : static_cast<QAbstractItemView*>(m_list_view);
target->selectionModel()->select(m_sort_model->index(0, 0), target->selectionModel()->select(m_sort_model->index(0, 0),
QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
target->setFocus(Qt::ShortcutFocusReason); target->setFocus(Qt::ShortcutFocusReason);
@ -1535,7 +1544,7 @@ void GameListWidget::showGameList()
Host::SetBaseBoolSettingValue("UI", "GameListGridView", false); Host::SetBaseBoolSettingValue("UI", "GameListGridView", false);
Host::CommitBaseSettingChanges(); Host::CommitBaseSettingChanges();
m_ui.stack->setCurrentIndex(0); m_ui.stack->setCurrentIndex(0);
setFocusProxy(m_table_view); setFocusProxy(m_list_view);
resizeTableViewColumnsToFit(); resizeTableViewColumnsToFit();
updateToolbar(); updateToolbar();
emit layoutChanged(); emit layoutChanged();
@ -1552,7 +1561,7 @@ void GameListWidget::showGameGrid()
Host::SetBaseBoolSettingValue("UI", "GameListGridView", true); Host::SetBaseBoolSettingValue("UI", "GameListGridView", true);
Host::CommitBaseSettingChanges(); Host::CommitBaseSettingChanges();
m_ui.stack->setCurrentIndex(1); m_ui.stack->setCurrentIndex(1);
setFocusProxy(m_list_view); setFocusProxy(m_grid_view);
updateToolbar(); updateToolbar();
emit layoutChanged(); emit layoutChanged();
} }
@ -1568,6 +1577,7 @@ void GameListWidget::setShowCoverTitles(bool enabled)
Host::SetBaseBoolSettingValue("UI", "GameListShowCoverTitles", enabled); Host::SetBaseBoolSettingValue("UI", "GameListShowCoverTitles", enabled);
Host::CommitBaseSettingChanges(); Host::CommitBaseSettingChanges();
m_model->setShowCoverTitles(enabled); m_model->setShowCoverTitles(enabled);
m_grid_view->updateLayout();
if (isShowingGameGrid()) if (isShowingGameGrid())
m_model->refresh(); m_model->refresh();
updateToolbar(); updateToolbar();
@ -1636,24 +1646,24 @@ void GameListWidget::resizeEvent(QResizeEvent* event)
void GameListWidget::resizeTableViewColumnsToFit() void GameListWidget::resizeTableViewColumnsToFit()
{ {
QtUtils::ResizeColumnsForTableView(m_table_view, { QtUtils::ResizeColumnsForTableView(m_list_view, {
45, // type 45, // type
80, // code 80, // code
-1, // title -1, // title
-1, // file title -1, // file title
200, // developer 200, // developer
200, // publisher 200, // publisher
200, // genre 200, // genre
50, // year 50, // year
100, // players 100, // players
85, // time played 85, // time played
85, // last played 85, // last played
80, // file size 80, // file size
80, // size 80, // size
55, // region 55, // region
100, // achievements 100, // achievements
100 // compatibility 100 // compatibility
}); });
} }
static TinyString getColumnVisibilitySettingsKeyName(int column) static TinyString getColumnVisibilitySettingsKeyName(int column)
@ -1686,7 +1696,7 @@ void GameListWidget::loadTableViewColumnVisibilitySettings()
{ {
const bool visible = Host::GetBaseBoolSettingValue("GameListTableView", getColumnVisibilitySettingsKeyName(column), const bool visible = Host::GetBaseBoolSettingValue("GameListTableView", getColumnVisibilitySettingsKeyName(column),
DEFAULT_VISIBILITY[column]); DEFAULT_VISIBILITY[column]);
m_table_view->setColumnHidden(column, !visible); m_list_view->setColumnHidden(column, !visible);
} }
} }
@ -1694,7 +1704,7 @@ void GameListWidget::saveTableViewColumnVisibilitySettings()
{ {
for (int column = 0; column < GameListModel::Column_Count; column++) for (int column = 0; column < GameListModel::Column_Count; column++)
{ {
const bool visible = !m_table_view->isColumnHidden(column); const bool visible = !m_list_view->isColumnHidden(column);
Host::SetBaseBoolSettingValue("GameListTableView", getColumnVisibilitySettingsKeyName(column), visible); Host::SetBaseBoolSettingValue("GameListTableView", getColumnVisibilitySettingsKeyName(column), visible);
Host::CommitBaseSettingChanges(); Host::CommitBaseSettingChanges();
} }
@ -1702,7 +1712,7 @@ void GameListWidget::saveTableViewColumnVisibilitySettings()
void GameListWidget::saveTableViewColumnVisibilitySettings(int column) void GameListWidget::saveTableViewColumnVisibilitySettings(int column)
{ {
const bool visible = !m_table_view->isColumnHidden(column); const bool visible = !m_list_view->isColumnHidden(column);
Host::SetBaseBoolSettingValue("GameListTableView", getColumnVisibilitySettingsKeyName(column), visible); Host::SetBaseBoolSettingValue("GameListTableView", getColumnVisibilitySettingsKeyName(column), visible);
Host::CommitBaseSettingChanges(); Host::CommitBaseSettingChanges();
} }
@ -1719,14 +1729,14 @@ void GameListWidget::loadTableViewColumnSortSettings()
Host::GetBaseBoolSettingValue("GameListTableView", "SortDescending", DEFAULT_SORT_DESCENDING); Host::GetBaseBoolSettingValue("GameListTableView", "SortDescending", DEFAULT_SORT_DESCENDING);
const Qt::SortOrder sort_order = sort_descending ? Qt::DescendingOrder : Qt::AscendingOrder; const Qt::SortOrder sort_order = sort_descending ? Qt::DescendingOrder : Qt::AscendingOrder;
m_sort_model->sort(sort_column, sort_order); m_sort_model->sort(sort_column, sort_order);
if (QHeaderView* hv = m_table_view->horizontalHeader()) if (QHeaderView* hv = m_list_view->horizontalHeader())
hv->setSortIndicator(sort_column, sort_order); hv->setSortIndicator(sort_column, sort_order);
} }
void GameListWidget::saveTableViewColumnSortSettings() void GameListWidget::saveTableViewColumnSortSettings()
{ {
const int sort_column = m_table_view->horizontalHeader()->sortIndicatorSection(); const int sort_column = m_list_view->horizontalHeader()->sortIndicatorSection();
const bool sort_descending = (m_table_view->horizontalHeader()->sortIndicatorOrder() == Qt::DescendingOrder); const bool sort_descending = (m_list_view->horizontalHeader()->sortIndicatorOrder() == Qt::DescendingOrder);
if (sort_column >= 0 && sort_column < GameListModel::Column_Count) if (sort_column >= 0 && sort_column < GameListModel::Column_Count)
{ {
@ -1741,10 +1751,10 @@ void GameListWidget::saveTableViewColumnSortSettings()
void GameListWidget::setTableViewColumnHidden(int column, bool hidden) void GameListWidget::setTableViewColumnHidden(int column, bool hidden)
{ {
DebugAssert(column < GameListModel::Column_Count); DebugAssert(column < GameListModel::Column_Count);
if (m_table_view->isColumnHidden(column) == hidden) if (m_list_view->isColumnHidden(column) == hidden)
return; return;
m_table_view->setColumnHidden(column, hidden); m_list_view->setColumnHidden(column, hidden);
saveTableViewColumnVisibilitySettings(column); saveTableViewColumnVisibilitySettings(column);
resizeTableViewColumnsToFit(); resizeTableViewColumnsToFit();
} }
@ -1753,7 +1763,7 @@ const GameList::Entry* GameListWidget::getSelectedEntry() const
{ {
if (m_ui.stack->currentIndex() == 0) if (m_ui.stack->currentIndex() == 0)
{ {
const QItemSelectionModel* selection_model = m_table_view->selectionModel(); const QItemSelectionModel* selection_model = m_list_view->selectionModel();
if (!selection_model->hasSelection()) if (!selection_model->hasSelection())
return nullptr; return nullptr;
@ -1769,7 +1779,7 @@ const GameList::Entry* GameListWidget::getSelectedEntry() const
} }
else else
{ {
const QItemSelectionModel* selection_model = m_list_view->selectionModel(); const QItemSelectionModel* selection_model = m_grid_view->selectionModel();
if (!selection_model->hasSelection()) if (!selection_model->hasSelection())
return nullptr; return nullptr;
@ -1781,7 +1791,7 @@ const GameList::Entry* GameListWidget::getSelectedEntry() const
} }
} }
GameListGridListView::GameListGridListView(QWidget* parent /*= nullptr*/) : QListView(parent) GameListGridListView::GameListGridListView(GameListModel* model, QWidget* parent) : QListView(parent), m_model(model)
{ {
} }
@ -1806,26 +1816,44 @@ void GameListGridListView::wheelEvent(QWheelEvent* e)
void GameListGridListView::resizeEvent(QResizeEvent* e) void GameListGridListView::resizeEvent(QResizeEvent* e)
{ {
updateLayout();
QListView::resizeEvent(e); QListView::resizeEvent(e);
updateLayout();
} }
void GameListGridListView::updateLayout() void GameListGridListView::updateLayout()
{ {
const int scrollbar_width = verticalScrollBar()->width(); const QScrollBar* const vertical_scrollbar = verticalScrollBar();
const int item_spacing = spacing(); const int scrollbar_width = vertical_scrollbar->isVisible() ? vertical_scrollbar->width() : 0;
const int icon_margin = style()->pixelMetric(QStyle::PM_FocusFrameHMargin, nullptr, this) * 2; const int icon_width = m_model->getCoverArtSize();
const int item_spacing = m_model->getCoverArtSpacing();
const int item_margin = style()->pixelMetric(QStyle::PM_FocusFrameHMargin, nullptr, this);
// Split margin+spacing evenly across both sides of each item.
// I hate this +2. Not sure what's not being accounted for, but without it the calculation is off by 2 pixels... // I hate this +2. Not sure what's not being accounted for, but without it the calculation is off by 2 pixels...
const int icon_width = iconSize().width() + icon_margin + item_spacing + 2; const int item_width = icon_width + item_margin + item_spacing + 2;
const int available_width = width() - scrollbar_width - item_spacing - icon_margin;
const int items_per_row = available_width / icon_width; // one line of text
const int margin = (available_width - (items_per_row * icon_width)) / 2; const int item_height = item_width + (m_model->getShowCoverTitles() ? QFontMetrics(font()).height() : 0);
const int available_width = width() - scrollbar_width;
const int num_columns = available_width / item_width;
const int num_rows = (height() + (item_height - 1)) / item_height;
const int margin = (available_width - (num_columns * item_width)) / 2;
setGridSize(QSize(item_width, item_height));
m_model->updateCacheSize(num_rows, num_columns);
m_horizontal_offset = margin; m_horizontal_offset = margin;
m_vertical_offset = item_spacing;
} }
int GameListGridListView::horizontalOffset() const int GameListGridListView::horizontalOffset() const
{ {
return QListView::horizontalOffset() - m_horizontal_offset; return QListView::horizontalOffset() - m_horizontal_offset;
} }
int GameListGridListView::verticalOffset() const
{
return QListView::verticalOffset() - m_vertical_offset;
}

View File

@ -94,11 +94,10 @@ public:
float getCoverScale() const { return m_cover_scale; } float getCoverScale() const { return m_cover_scale; }
void setCoverScale(float scale); void setCoverScale(float scale);
int getCoverArtWidth() const; int getCoverArtSize() const;
int getCoverArtHeight() const;
int getCoverArtSpacing() const; int getCoverArtSpacing() const;
void refreshCovers(); void refreshCovers();
void updateCacheSize(int width, int height); void updateCacheSize(int num_rows, int num_columns);
Q_SIGNALS: Q_SIGNALS:
void coverScaleChanged(); void coverScaleChanged();
@ -157,10 +156,11 @@ class GameListGridListView final : public QListView
Q_OBJECT Q_OBJECT
public: public:
GameListGridListView(QWidget* parent = nullptr); GameListGridListView(GameListModel* model, QWidget* parent);
void updateLayout(); void updateLayout();
int horizontalOffset() const override; int horizontalOffset() const override;
int verticalOffset() const override;
Q_SIGNALS: Q_SIGNALS:
void zoomOut(); void zoomOut();
@ -171,7 +171,9 @@ protected:
void resizeEvent(QResizeEvent* e) override; void resizeEvent(QResizeEvent* e) override;
private: private:
GameListModel* m_model = nullptr;
int m_horizontal_offset = 0; int m_horizontal_offset = 0;
int m_vertical_offset = 0;
}; };
class GameListWidget final : public QWidget class GameListWidget final : public QWidget
@ -255,8 +257,8 @@ private:
GameListModel* m_model = nullptr; GameListModel* m_model = nullptr;
GameListSortModel* m_sort_model = nullptr; GameListSortModel* m_sort_model = nullptr;
QTableView* m_table_view = nullptr; QTableView* m_list_view = nullptr;
GameListGridListView* m_list_view = nullptr; GameListGridListView* m_grid_view = nullptr;
QWidget* m_empty_widget = nullptr; QWidget* m_empty_widget = nullptr;
Ui::EmptyGameListWidget m_empty_ui; Ui::EmptyGameListWidget m_empty_ui;