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/file_system.h"
#include "common/log.h"
#include "common/path.h"
#include "common/string_util.h"
@ -32,6 +33,8 @@
#include <QtWidgets/QStyledItemDelegate>
#include <algorithm>
LOG_CHANNEL(GameList);
static constexpr float MIN_SCALE = 0.1f;
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",
"Last Played", "Size", "File Size", "Region", "Achievements", "Compatibility", "Cover"}};
static constexpr int COVER_ART_WIDTH = 512;
static constexpr int COVER_ART_HEIGHT = 512;
static constexpr int COVER_ART_SIZE = 512;
static constexpr int COVER_ART_SPACING = 32;
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)
{
@ -159,11 +162,11 @@ void GameListModel::setCoverScale(float scale)
if (loading_image.load(QStringLiteral("%1/images/placeholder.png").arg(QtHost::GetResourcesBasePath())))
{
loading_image.setDevicePixelRatio(dpr);
resizeAndPadImage(&loading_image, getCoverArtWidth(), getCoverArtHeight(), false);
resizeAndPadImage(&loading_image, getCoverArtSize(), getCoverArtSize(), false);
}
else
{
loading_image = QImage(getCoverArtWidth(), getCoverArtHeight(), QImage::Format_RGB32);
loading_image = QImage(getCoverArtSize(), getCoverArtSize(), QImage::Format_RGB32);
loading_image.setDevicePixelRatio(dpr);
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())))
{
m_placeholder_image.setDevicePixelRatio(dpr);
resizeAndPadImage(&m_placeholder_image, getCoverArtWidth(), getCoverArtHeight(), false);
resizeAndPadImage(&m_placeholder_image, getCoverArtSize(), getCoverArtSize(), false);
}
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.fill(QColor(0, 0, 0, 0));
}
@ -191,14 +194,13 @@ void GameListModel::refreshCovers()
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.
const int cover_width = getCoverArtWidth();
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)));
m_cover_pixmap_cache.SetMaxCapacity(static_cast<int>(std::max(num_items, MIN_COVER_CACHE_SIZE)));
}
void GameListModel::reloadThemeSpecificImages()
@ -210,8 +212,8 @@ void GameListModel::reloadThemeSpecificImages()
void GameListModel::loadOrGenerateCover(const GameList::Entry* ge)
{
QtAsyncTask::create(this, [path = ge->path, serial = ge->serial, title = ge->title,
placeholder_image = m_placeholder_image, list = this, width = getCoverArtWidth(),
height = getCoverArtHeight(), scale = m_cover_scale,
placeholder_image = m_placeholder_image, list = this, width = getCoverArtSize(),
height = getCoverArtSize(), scale = m_cover_scale,
dpr = qApp->devicePixelRatio()]() mutable {
QImage image;
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);
}
int GameListModel::getCoverArtWidth() const
int GameListModel::getCoverArtSize() const
{
return std::max(static_cast<int>(static_cast<float>(COVER_ART_WIDTH) * m_cover_scale), 1);
}
int GameListModel::getCoverArtHeight() const
{
return std::max(static_cast<int>(static_cast<float>(COVER_ART_HEIGHT) * m_cover_scale), 1);
return std::max(static_cast<int>(static_cast<float>(COVER_ART_SIZE) * m_cover_scale), 1);
}
int GameListModel::getCoverArtSpacing() const
@ -1114,6 +1111,25 @@ private:
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
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 show_game_icons = Host::GetBaseBoolSettingValue("UI", "GameListShowGameIcons", true);
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->setSourceModel(m_model);
@ -1165,65 +1180,64 @@ void GameListWidget::initialize()
connect(m_ui.searchText, &QLineEdit::returnPressed, this, &GameListWidget::onSearchReturnPressed);
GameListCenterIconStyleDelegate* center_icon_delegate = new GameListCenterIconStyleDelegate(this);
m_table_view = new QTableView(m_ui.stack);
m_table_view->setModel(m_sort_model);
m_table_view->setSortingEnabled(true);
m_table_view->setSelectionMode(QAbstractItemView::SingleSelection);
m_table_view->setSelectionBehavior(QAbstractItemView::SelectRows);
m_table_view->setContextMenuPolicy(Qt::CustomContextMenu);
m_table_view->setAlternatingRowColors(true);
m_table_view->setShowGrid(false);
m_table_view->setCurrentIndex({});
m_table_view->horizontalHeader()->setHighlightSections(false);
m_table_view->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu);
m_table_view->verticalHeader()->hide();
m_table_view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
m_table_view->setVerticalScrollMode(QAbstractItemView::ScrollMode::ScrollPerPixel);
m_table_view->setItemDelegateForColumn(GameListModel::Column_Icon, center_icon_delegate);
m_table_view->setItemDelegateForColumn(GameListModel::Column_Region, center_icon_delegate);
m_table_view->setItemDelegateForColumn(GameListModel::Column_Achievements,
new GameListAchievementsStyleDelegate(this, m_model, m_sort_model));
m_list_view = new QTableView(m_ui.stack);
m_list_view->setModel(m_sort_model);
m_list_view->setSortingEnabled(true);
m_list_view->setSelectionMode(QAbstractItemView::SingleSelection);
m_list_view->setSelectionBehavior(QAbstractItemView::SelectRows);
m_list_view->setContextMenuPolicy(Qt::CustomContextMenu);
m_list_view->setAlternatingRowColors(true);
m_list_view->setShowGrid(false);
m_list_view->setCurrentIndex({});
m_list_view->horizontalHeader()->setHighlightSections(false);
m_list_view->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu);
m_list_view->verticalHeader()->hide();
m_list_view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
m_list_view->setVerticalScrollMode(QAbstractItemView::ScrollMode::ScrollPerPixel);
m_list_view->setItemDelegateForColumn(GameListModel::Column_Icon, center_icon_delegate);
m_list_view->setItemDelegateForColumn(GameListModel::Column_Region, center_icon_delegate);
m_list_view->setItemDelegateForColumn(GameListModel::Column_Achievements,
new GameListAchievementsStyleDelegate(this, m_model, m_sort_model));
loadTableViewColumnVisibilitySettings();
loadTableViewColumnSortSettings();
connect(m_table_view->selectionModel(), &QItemSelectionModel::currentChanged, this,
connect(m_list_view->selectionModel(), &QItemSelectionModel::currentChanged, this,
&GameListWidget::onSelectionModelCurrentChanged);
connect(m_table_view, &QTableView::activated, this, &GameListWidget::onTableViewItemActivated);
connect(m_table_view, &QTableView::customContextMenuRequested, this,
&GameListWidget::onTableViewContextMenuRequested);
connect(m_table_view->horizontalHeader(), &QHeaderView::customContextMenuRequested, this,
connect(m_list_view, &QTableView::activated, this, &GameListWidget::onTableViewItemActivated);
connect(m_list_view, &QTableView::customContextMenuRequested, this, &GameListWidget::onTableViewContextMenuRequested);
connect(m_list_view->horizontalHeader(), &QHeaderView::customContextMenuRequested, this,
&GameListWidget::onTableViewHeaderContextMenuRequested);
connect(m_table_view->horizontalHeader(), &QHeaderView::sortIndicatorChanged, this,
connect(m_list_view->horizontalHeader(), &QHeaderView::sortIndicatorChanged, this,
&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_list_view->setModel(m_sort_model);
m_list_view->setModelColumn(GameListModel::Column_Cover);
m_list_view->setSelectionMode(QAbstractItemView::SingleSelection);
m_list_view->setViewMode(QListView::IconMode);
m_list_view->setResizeMode(QListView::Adjust);
m_list_view->setUniformItemSizes(true);
m_list_view->setItemAlignment(Qt::AlignHCenter);
m_list_view->setContextMenuPolicy(Qt::CustomContextMenu);
m_list_view->setFrameStyle(QFrame::NoFrame);
m_list_view->setVerticalScrollMode(QAbstractItemView::ScrollMode::ScrollPerPixel);
m_list_view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
m_list_view->verticalScrollBar()->setSingleStep(15);
m_grid_view = new GameListGridListView(m_model, m_ui.stack);
m_grid_view->setModel(m_sort_model);
m_grid_view->setModelColumn(GameListModel::Column_Cover);
m_grid_view->setSelectionMode(QAbstractItemView::SingleSelection);
m_grid_view->setViewMode(QListView::IconMode);
m_grid_view->setResizeMode(QListView::Adjust);
m_grid_view->setUniformItemSizes(true);
m_grid_view->setItemAlignment(Qt::AlignHCenter);
m_grid_view->setContextMenuPolicy(Qt::CustomContextMenu);
m_grid_view->setFrameStyle(QFrame::NoFrame);
m_grid_view->setVerticalScrollMode(QAbstractItemView::ScrollMode::ScrollPerPixel);
m_grid_view->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
m_grid_view->verticalScrollBar()->setSingleStep(15);
onCoverScaleChanged();
connect(m_list_view->selectionModel(), &QItemSelectionModel::currentChanged, this,
connect(m_grid_view->selectionModel(), &QItemSelectionModel::currentChanged, this,
&GameListWidget::onSelectionModelCurrentChanged);
connect(m_list_view, &GameListGridListView::zoomIn, this, &GameListWidget::gridZoomIn);
connect(m_list_view, &GameListGridListView::zoomOut, this, &GameListWidget::gridZoomOut);
connect(m_list_view, &QListView::activated, this, &GameListWidget::onListViewItemActivated);
connect(m_list_view, &QListView::customContextMenuRequested, this, &GameListWidget::onListViewContextMenuRequested);
connect(m_grid_view, &GameListGridListView::zoomIn, this, &GameListWidget::gridZoomIn);
connect(m_grid_view, &GameListGridListView::zoomOut, this, &GameListWidget::gridZoomOut);
connect(m_grid_view, &QListView::activated, this, &GameListWidget::onListViewItemActivated);
connect(m_grid_view, &QListView::customContextMenuRequested, this, &GameListWidget::onListViewContextMenuRequested);
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_ui.setupUi(m_empty_widget);
@ -1237,7 +1251,7 @@ void GameListWidget::initialize()
m_ui.stack->setCurrentIndex(1);
else
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();
resizeTableViewColumnsToFit();
@ -1321,7 +1335,7 @@ void GameListWidget::updateBackground(bool reload_image)
if (m_background_image.isNull())
{
m_ui.stack->setPalette(palette());
m_table_view->setAlternatingRowColors(true);
m_list_view->setAlternatingRowColors(true);
return;
}
@ -1336,7 +1350,7 @@ void GameListWidget::updateBackground(bool reload_image)
QPalette new_palette(m_ui.stack->palette());
new_palette.setBrush(QPalette::Base, QPixmap::fromImage(image));
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);
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)
@ -1408,7 +1422,7 @@ void GameListWidget::onTableViewItemActivated(const QModelIndex& index)
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)
@ -1422,7 +1436,7 @@ void GameListWidget::onListViewItemActivated(const QModelIndex& index)
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)
@ -1436,15 +1450,15 @@ void GameListWidget::onTableViewHeaderContextMenuRequested(const QPoint& point)
QAction* action = menu.addAction(m_model->getColumnDisplayName(column));
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) {
m_table_view->setColumnHidden(column, !enabled);
m_list_view->setColumnHidden(column, !enabled);
saveTableViewColumnVisibilitySettings(column);
resizeTableViewColumnsToFit();
});
}
menu.exec(m_table_view->mapToGlobal(point));
menu.exec(m_list_view->mapToGlobal(point));
}
void GameListWidget::onTableViewHeaderSortIndicatorChanged(int, Qt::SortOrder)
@ -1454,16 +1468,11 @@ void GameListWidget::onTableViewHeaderSortIndicatorChanged(int, Qt::SortOrder)
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;
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)
@ -1518,7 +1527,7 @@ void GameListWidget::onSearchReturnPressed()
return;
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),
QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
target->setFocus(Qt::ShortcutFocusReason);
@ -1535,7 +1544,7 @@ void GameListWidget::showGameList()
Host::SetBaseBoolSettingValue("UI", "GameListGridView", false);
Host::CommitBaseSettingChanges();
m_ui.stack->setCurrentIndex(0);
setFocusProxy(m_table_view);
setFocusProxy(m_list_view);
resizeTableViewColumnsToFit();
updateToolbar();
emit layoutChanged();
@ -1552,7 +1561,7 @@ void GameListWidget::showGameGrid()
Host::SetBaseBoolSettingValue("UI", "GameListGridView", true);
Host::CommitBaseSettingChanges();
m_ui.stack->setCurrentIndex(1);
setFocusProxy(m_list_view);
setFocusProxy(m_grid_view);
updateToolbar();
emit layoutChanged();
}
@ -1568,6 +1577,7 @@ void GameListWidget::setShowCoverTitles(bool enabled)
Host::SetBaseBoolSettingValue("UI", "GameListShowCoverTitles", enabled);
Host::CommitBaseSettingChanges();
m_model->setShowCoverTitles(enabled);
m_grid_view->updateLayout();
if (isShowingGameGrid())
m_model->refresh();
updateToolbar();
@ -1636,24 +1646,24 @@ void GameListWidget::resizeEvent(QResizeEvent* event)
void GameListWidget::resizeTableViewColumnsToFit()
{
QtUtils::ResizeColumnsForTableView(m_table_view, {
45, // type
80, // code
-1, // title
-1, // file title
200, // developer
200, // publisher
200, // genre
50, // year
100, // players
85, // time played
85, // last played
80, // file size
80, // size
55, // region
100, // achievements
100 // compatibility
});
QtUtils::ResizeColumnsForTableView(m_list_view, {
45, // type
80, // code
-1, // title
-1, // file title
200, // developer
200, // publisher
200, // genre
50, // year
100, // players
85, // time played
85, // last played
80, // file size
80, // size
55, // region
100, // achievements
100 // compatibility
});
}
static TinyString getColumnVisibilitySettingsKeyName(int column)
@ -1686,7 +1696,7 @@ void GameListWidget::loadTableViewColumnVisibilitySettings()
{
const bool visible = Host::GetBaseBoolSettingValue("GameListTableView", getColumnVisibilitySettingsKeyName(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++)
{
const bool visible = !m_table_view->isColumnHidden(column);
const bool visible = !m_list_view->isColumnHidden(column);
Host::SetBaseBoolSettingValue("GameListTableView", getColumnVisibilitySettingsKeyName(column), visible);
Host::CommitBaseSettingChanges();
}
@ -1702,7 +1712,7 @@ void GameListWidget::saveTableViewColumnVisibilitySettings()
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::CommitBaseSettingChanges();
}
@ -1719,14 +1729,14 @@ void GameListWidget::loadTableViewColumnSortSettings()
Host::GetBaseBoolSettingValue("GameListTableView", "SortDescending", DEFAULT_SORT_DESCENDING);
const Qt::SortOrder sort_order = sort_descending ? Qt::DescendingOrder : Qt::AscendingOrder;
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);
}
void GameListWidget::saveTableViewColumnSortSettings()
{
const int sort_column = m_table_view->horizontalHeader()->sortIndicatorSection();
const bool sort_descending = (m_table_view->horizontalHeader()->sortIndicatorOrder() == Qt::DescendingOrder);
const int sort_column = m_list_view->horizontalHeader()->sortIndicatorSection();
const bool sort_descending = (m_list_view->horizontalHeader()->sortIndicatorOrder() == Qt::DescendingOrder);
if (sort_column >= 0 && sort_column < GameListModel::Column_Count)
{
@ -1741,10 +1751,10 @@ void GameListWidget::saveTableViewColumnSortSettings()
void GameListWidget::setTableViewColumnHidden(int column, bool hidden)
{
DebugAssert(column < GameListModel::Column_Count);
if (m_table_view->isColumnHidden(column) == hidden)
if (m_list_view->isColumnHidden(column) == hidden)
return;
m_table_view->setColumnHidden(column, hidden);
m_list_view->setColumnHidden(column, hidden);
saveTableViewColumnVisibilitySettings(column);
resizeTableViewColumnsToFit();
}
@ -1753,7 +1763,7 @@ const GameList::Entry* GameListWidget::getSelectedEntry() const
{
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())
return nullptr;
@ -1769,7 +1779,7 @@ const GameList::Entry* GameListWidget::getSelectedEntry() const
}
else
{
const QItemSelectionModel* selection_model = m_list_view->selectionModel();
const QItemSelectionModel* selection_model = m_grid_view->selectionModel();
if (!selection_model->hasSelection())
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)
{
updateLayout();
QListView::resizeEvent(e);
updateLayout();
}
void GameListGridListView::updateLayout()
{
const int scrollbar_width = verticalScrollBar()->width();
const int item_spacing = spacing();
const int icon_margin = style()->pixelMetric(QStyle::PM_FocusFrameHMargin, nullptr, this) * 2;
const QScrollBar* const vertical_scrollbar = verticalScrollBar();
const int scrollbar_width = vertical_scrollbar->isVisible() ? vertical_scrollbar->width() : 0;
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...
const int icon_width = iconSize().width() + icon_margin + item_spacing + 2;
const int available_width = width() - scrollbar_width - item_spacing - icon_margin;
const int items_per_row = available_width / icon_width;
const int margin = (available_width - (items_per_row * icon_width)) / 2;
const int item_width = icon_width + item_margin + item_spacing + 2;
// one line of text
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_vertical_offset = item_spacing;
}
int GameListGridListView::horizontalOffset() const
{
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; }
void setCoverScale(float scale);
int getCoverArtWidth() const;
int getCoverArtHeight() const;
int getCoverArtSize() const;
int getCoverArtSpacing() const;
void refreshCovers();
void updateCacheSize(int width, int height);
void updateCacheSize(int num_rows, int num_columns);
Q_SIGNALS:
void coverScaleChanged();
@ -157,10 +156,11 @@ class GameListGridListView final : public QListView
Q_OBJECT
public:
GameListGridListView(QWidget* parent = nullptr);
GameListGridListView(GameListModel* model, QWidget* parent);
void updateLayout();
int horizontalOffset() const override;
int verticalOffset() const override;
Q_SIGNALS:
void zoomOut();
@ -171,7 +171,9 @@ protected:
void resizeEvent(QResizeEvent* e) override;
private:
GameListModel* m_model = nullptr;
int m_horizontal_offset = 0;
int m_vertical_offset = 0;
};
class GameListWidget final : public QWidget
@ -255,8 +257,8 @@ private:
GameListModel* m_model = nullptr;
GameListSortModel* m_sort_model = nullptr;
QTableView* m_table_view = nullptr;
GameListGridListView* m_list_view = nullptr;
QTableView* m_list_view = nullptr;
GameListGridListView* m_grid_view = nullptr;
QWidget* m_empty_widget = nullptr;
Ui::EmptyGameListWidget m_empty_ui;