mirror of
https://git.citron-emu.org/citron/emulator
synced 2025-12-19 18:53:32 +00:00
ui: Add modern toolbar to game list with view controls and sorting
Add a toolbar to the game list with: - List/Grid view toggle buttons with icons and rounded corners - Icon size slider for adjusting game icon sizes in real-time - A-Z sort button that toggles between ascending/descending order - Rounded corners styling throughout for a modern appearance Key improvements: - Fix games vanishing when adjusting slider during filtered search by updating icons in-place instead of recreating the model - Preserve scroll position and selection when updating icon sizes - Reuse filtered model instead of deleting/recreating to prevent view flicker - Ensure consistent rounded icon rendering at exact target sizes - Sync sort button state with Name column header sort order - Preserve active filters when repopulating the game list The toolbar uses themed icons with Qt standard icon fallbacks for cross-platform compatibility. Signed-off-by: Zephyron <zephyron@citron-emu.org>
This commit is contained in:
@@ -7,11 +7,16 @@
|
|||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QHeaderView>
|
#include <QHeaderView>
|
||||||
|
#include <QIcon>
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
|
#include <QPainter>
|
||||||
|
#include <QPainterPath>
|
||||||
|
#include <QScrollBar>
|
||||||
|
#include <QStyle>
|
||||||
#include <QThreadPool>
|
#include <QThreadPool>
|
||||||
#include <QToolButton>
|
#include <QToolButton>
|
||||||
#include <QtConcurrent/QtConcurrent>
|
#include <QtConcurrent/QtConcurrent>
|
||||||
@@ -122,16 +127,29 @@ GameListSearchField::GameListSearchField(GameList* parent) : QWidget{parent} {
|
|||||||
edit_filter->clear();
|
edit_filter->clear();
|
||||||
edit_filter->installEventFilter(key_release_eater);
|
edit_filter->installEventFilter(key_release_eater);
|
||||||
edit_filter->setClearButtonEnabled(true);
|
edit_filter->setClearButtonEnabled(true);
|
||||||
|
// Add rounded corners styling to the search field
|
||||||
|
edit_filter->setStyleSheet(QStringLiteral(
|
||||||
|
"QLineEdit {"
|
||||||
|
" border: 1px solid palette(mid);"
|
||||||
|
" border-radius: 6px;"
|
||||||
|
" padding: 4px 8px;"
|
||||||
|
" background: palette(base);"
|
||||||
|
"}"
|
||||||
|
"QLineEdit:focus {"
|
||||||
|
" border: 1px solid palette(highlight);"
|
||||||
|
" background: palette(base);"
|
||||||
|
"}"
|
||||||
|
));
|
||||||
connect(edit_filter, &QLineEdit::textChanged, parent, &GameList::OnTextChanged);
|
connect(edit_filter, &QLineEdit::textChanged, parent, &GameList::OnTextChanged);
|
||||||
label_filter_result = new QLabel;
|
label_filter_result = new QLabel;
|
||||||
button_filter_close = new QToolButton(this);
|
button_filter_close = new QToolButton(this);
|
||||||
button_filter_close->setText(QStringLiteral("X"));
|
button_filter_close->setText(QStringLiteral("X"));
|
||||||
button_filter_close->setCursor(Qt::ArrowCursor);
|
button_filter_close->setCursor(Qt::ArrowCursor);
|
||||||
button_filter_close->setStyleSheet(
|
button_filter_close->setStyleSheet(
|
||||||
QStringLiteral("QToolButton{ border: none; padding: 0px; color: "
|
QStringLiteral("QToolButton{ border: 1px solid palette(mid); border-radius: 4px; padding: 4px 8px; color: "
|
||||||
"#000000; font-weight: bold; background: #F0F0F0; }"
|
"palette(text); font-weight: bold; background: palette(button); }"
|
||||||
"QToolButton:hover{ border: none; padding: 0px; color: "
|
"QToolButton:hover{ border: 1px solid palette(highlight); color: "
|
||||||
"#EEEEEE; font-weight: bold; background: #E81123}"));
|
"palette(highlighted-text); background: palette(highlight)}"));
|
||||||
connect(button_filter_close, &QToolButton::clicked, parent, &GameList::OnFilterCloseClicked);
|
connect(button_filter_close, &QToolButton::clicked, parent, &GameList::OnFilterCloseClicked);
|
||||||
layout_filter->setSpacing(10);
|
layout_filter->setSpacing(10);
|
||||||
layout_filter->addWidget(label_filter);
|
layout_filter->addWidget(label_filter);
|
||||||
@@ -177,12 +195,26 @@ void GameList::OnTextChanged(const QString& new_text) {
|
|||||||
|
|
||||||
void GameList::FilterGridView(const QString& filter_text) {
|
void GameList::FilterGridView(const QString& filter_text) {
|
||||||
QStandardItemModel* hierarchical_model = item_model;
|
QStandardItemModel* hierarchical_model = item_model;
|
||||||
if (QAbstractItemModel* old_model = list_view->model()) {
|
QStandardItemModel* flat_model = nullptr;
|
||||||
if (old_model != item_model) {
|
|
||||||
old_model->deleteLater();
|
// Check if we can reuse the existing model
|
||||||
|
QAbstractItemModel* current_model = list_view->model();
|
||||||
|
if (current_model && current_model != item_model) {
|
||||||
|
QStandardItemModel* existing_flat = qobject_cast<QStandardItemModel*>(current_model);
|
||||||
|
if (existing_flat) {
|
||||||
|
// Clear existing model instead of deleting it to avoid view flicker
|
||||||
|
existing_flat->clear();
|
||||||
|
flat_model = existing_flat;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
QStandardItemModel* flat_model = new QStandardItemModel(this);
|
|
||||||
|
if (!flat_model) {
|
||||||
|
// Delete old model if it exists and create new one
|
||||||
|
if (current_model && current_model != item_model) {
|
||||||
|
current_model->deleteLater();
|
||||||
|
}
|
||||||
|
flat_model = new QStandardItemModel(this);
|
||||||
|
}
|
||||||
int visible_count = 0;
|
int visible_count = 0;
|
||||||
int total_count = 0;
|
int total_count = 0;
|
||||||
for (int i = 0; i < hierarchical_model->rowCount(); ++i) {
|
for (int i = 0; i < hierarchical_model->rowCount(); ++i) {
|
||||||
@@ -225,6 +257,39 @@ void GameList::FilterGridView(const QString& filter_text) {
|
|||||||
list_view->setModel(flat_model);
|
list_view->setModel(flat_model);
|
||||||
const u32 icon_size = UISettings::values.game_icon_size.GetValue();
|
const u32 icon_size = UISettings::values.game_icon_size.GetValue();
|
||||||
list_view->setGridSize(QSize(icon_size + 60, icon_size + 80));
|
list_view->setGridSize(QSize(icon_size + 60, icon_size + 80));
|
||||||
|
// Set sort role and sort the filtered model
|
||||||
|
flat_model->setSortRole(GameListItemPath::SortRole);
|
||||||
|
flat_model->sort(0, current_sort_order);
|
||||||
|
// Update icon sizes in the model - ensure all icons are consistently sized with rounded corners
|
||||||
|
for (int i = 0; i < flat_model->rowCount(); ++i) {
|
||||||
|
QStandardItem* item = flat_model->item(i);
|
||||||
|
if (item) {
|
||||||
|
QVariant icon_data = item->data(Qt::DecorationRole);
|
||||||
|
if (icon_data.isValid() && icon_data.type() == QVariant::Pixmap) {
|
||||||
|
QPixmap pixmap = icon_data.value<QPixmap>();
|
||||||
|
if (!pixmap.isNull()) {
|
||||||
|
// Always recreate the rounded icon at the exact target size for consistency
|
||||||
|
QPixmap rounded(icon_size, icon_size);
|
||||||
|
rounded.fill(Qt::transparent);
|
||||||
|
|
||||||
|
QPainter painter(&rounded);
|
||||||
|
painter.setRenderHint(QPainter::Antialiasing);
|
||||||
|
|
||||||
|
// Create rounded rectangle clipping path
|
||||||
|
const int radius = icon_size / 8;
|
||||||
|
QPainterPath path;
|
||||||
|
path.addRoundedRect(0, 0, icon_size, icon_size, radius, radius);
|
||||||
|
painter.setClipPath(path);
|
||||||
|
|
||||||
|
// Scale the source pixmap to fill the icon size exactly
|
||||||
|
QPixmap scaled = pixmap.scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
|
||||||
|
painter.drawPixmap(0, 0, scaled);
|
||||||
|
|
||||||
|
item->setData(rounded, Qt::DecorationRole);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
search_field->setFilterResult(visible_count, total_count);
|
search_field->setFilterResult(visible_count, total_count);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -363,6 +428,13 @@ play_time_manager{play_time_manager_}, system{system_} {
|
|||||||
connect(tree_view, &QTreeView::customContextMenuRequested, this, &GameList::PopupContextMenu);
|
connect(tree_view, &QTreeView::customContextMenuRequested, this, &GameList::PopupContextMenu);
|
||||||
connect(tree_view, &QTreeView::expanded, this, &GameList::OnItemExpanded);
|
connect(tree_view, &QTreeView::expanded, this, &GameList::OnItemExpanded);
|
||||||
connect(tree_view, &QTreeView::collapsed, this, &GameList::OnItemExpanded);
|
connect(tree_view, &QTreeView::collapsed, this, &GameList::OnItemExpanded);
|
||||||
|
// Sync sort button with Name column header sort order
|
||||||
|
connect(tree_view->header(), &QHeaderView::sortIndicatorChanged, [this](int logicalIndex, Qt::SortOrder order) {
|
||||||
|
if (logicalIndex == COLUMN_NAME) {
|
||||||
|
current_sort_order = order;
|
||||||
|
UpdateSortButtonIcon();
|
||||||
|
}
|
||||||
|
});
|
||||||
connect(list_view, &QListView::activated, this, &GameList::ValidateEntry);
|
connect(list_view, &QListView::activated, this, &GameList::ValidateEntry);
|
||||||
connect(list_view, &QListView::customContextMenuRequested, this, &GameList::PopupContextMenu);
|
connect(list_view, &QListView::customContextMenuRequested, this, &GameList::PopupContextMenu);
|
||||||
connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent, [this](Qt::Key key) {
|
connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent, [this](Qt::Key key) {
|
||||||
@@ -382,11 +454,228 @@ play_time_manager{play_time_manager_}, system{system_} {
|
|||||||
qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>");
|
qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>");
|
||||||
qRegisterMetaType<std::map<u64, std::pair<int, int>>>("std::map<u64, std::pair<int, int>>");
|
qRegisterMetaType<std::map<u64, std::pair<int, int>>>("std::map<u64, std::pair<int, int>>");
|
||||||
|
|
||||||
|
// Create toolbar
|
||||||
|
toolbar = new QWidget(this);
|
||||||
|
toolbar_layout = new QHBoxLayout(toolbar);
|
||||||
|
toolbar_layout->setContentsMargins(8, 6, 8, 6);
|
||||||
|
toolbar_layout->setSpacing(6);
|
||||||
|
|
||||||
|
// List view button - icon-only with rounded corners
|
||||||
|
btn_list_view = new QToolButton(toolbar);
|
||||||
|
QIcon list_icon = QIcon::fromTheme(QStringLiteral("view-list-details"));
|
||||||
|
if (list_icon.isNull()) {
|
||||||
|
list_icon = QIcon::fromTheme(QStringLiteral("view-list"));
|
||||||
|
}
|
||||||
|
if (list_icon.isNull()) {
|
||||||
|
list_icon = style()->standardIcon(QStyle::SP_FileDialogListView);
|
||||||
|
}
|
||||||
|
btn_list_view->setIcon(list_icon);
|
||||||
|
btn_list_view->setToolTip(tr("List View"));
|
||||||
|
btn_list_view->setCheckable(true);
|
||||||
|
btn_list_view->setChecked(!UISettings::values.game_list_grid_view.GetValue());
|
||||||
|
btn_list_view->setAutoRaise(true);
|
||||||
|
btn_list_view->setIconSize(QSize(16, 16));
|
||||||
|
btn_list_view->setFixedSize(32, 32);
|
||||||
|
btn_list_view->setStyleSheet(QStringLiteral(
|
||||||
|
"QToolButton {"
|
||||||
|
" border: 1px solid palette(mid);"
|
||||||
|
" border-radius: 4px;"
|
||||||
|
" background: palette(button);"
|
||||||
|
"}"
|
||||||
|
"QToolButton:hover {"
|
||||||
|
" background: palette(light);"
|
||||||
|
"}"
|
||||||
|
"QToolButton:checked {"
|
||||||
|
" background: palette(highlight);"
|
||||||
|
" border-color: palette(highlight);"
|
||||||
|
"}"
|
||||||
|
));
|
||||||
|
connect(btn_list_view, &QToolButton::clicked, [this]() {
|
||||||
|
SetViewMode(false);
|
||||||
|
btn_list_view->setChecked(true);
|
||||||
|
btn_grid_view->setChecked(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Grid view button - icon-only with rounded corners
|
||||||
|
btn_grid_view = new QToolButton(toolbar);
|
||||||
|
QIcon grid_icon = QIcon::fromTheme(QStringLiteral("view-grid"));
|
||||||
|
if (grid_icon.isNull()) {
|
||||||
|
grid_icon = QIcon::fromTheme(QStringLiteral("view-grid-details"));
|
||||||
|
}
|
||||||
|
if (grid_icon.isNull()) {
|
||||||
|
grid_icon = style()->standardIcon(QStyle::SP_FileDialogDetailedView);
|
||||||
|
}
|
||||||
|
btn_grid_view->setIcon(grid_icon);
|
||||||
|
btn_grid_view->setToolTip(tr("Grid View"));
|
||||||
|
btn_grid_view->setCheckable(true);
|
||||||
|
btn_grid_view->setChecked(UISettings::values.game_list_grid_view.GetValue());
|
||||||
|
btn_grid_view->setAutoRaise(true);
|
||||||
|
btn_grid_view->setIconSize(QSize(16, 16));
|
||||||
|
btn_grid_view->setFixedSize(32, 32);
|
||||||
|
btn_grid_view->setStyleSheet(QStringLiteral(
|
||||||
|
"QToolButton {"
|
||||||
|
" border: 1px solid palette(mid);"
|
||||||
|
" border-radius: 4px;"
|
||||||
|
" background: palette(button);"
|
||||||
|
"}"
|
||||||
|
"QToolButton:hover {"
|
||||||
|
" background: palette(light);"
|
||||||
|
"}"
|
||||||
|
"QToolButton:checked {"
|
||||||
|
" background: palette(highlight);"
|
||||||
|
" border-color: palette(highlight);"
|
||||||
|
"}"
|
||||||
|
));
|
||||||
|
connect(btn_grid_view, &QToolButton::clicked, [this]() {
|
||||||
|
SetViewMode(true);
|
||||||
|
btn_list_view->setChecked(false);
|
||||||
|
btn_grid_view->setChecked(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Title/Icon size slider - compact with rounded corners
|
||||||
|
slider_title_size = new QSlider(Qt::Horizontal, toolbar);
|
||||||
|
slider_title_size->setMinimum(32);
|
||||||
|
slider_title_size->setMaximum(256);
|
||||||
|
slider_title_size->setValue(static_cast<int>(UISettings::values.game_icon_size.GetValue()));
|
||||||
|
slider_title_size->setToolTip(tr("Game Icon Size"));
|
||||||
|
slider_title_size->setMaximumWidth(120);
|
||||||
|
slider_title_size->setMinimumWidth(120);
|
||||||
|
slider_title_size->setStyleSheet(QStringLiteral(
|
||||||
|
"QSlider::groove:horizontal {"
|
||||||
|
" border: 1px solid palette(mid);"
|
||||||
|
" height: 4px;"
|
||||||
|
" background: palette(base);"
|
||||||
|
" border-radius: 2px;"
|
||||||
|
"}"
|
||||||
|
"QSlider::handle:horizontal {"
|
||||||
|
" background: palette(button);"
|
||||||
|
" border: 1px solid palette(mid);"
|
||||||
|
" width: 12px;"
|
||||||
|
" height: 12px;"
|
||||||
|
" margin: -4px 0;"
|
||||||
|
" border-radius: 6px;"
|
||||||
|
"}"
|
||||||
|
"QSlider::handle:horizontal:hover {"
|
||||||
|
" background: palette(light);"
|
||||||
|
"}"
|
||||||
|
));
|
||||||
|
connect(slider_title_size, &QSlider::valueChanged, [this](int value) {
|
||||||
|
// Update game icon size
|
||||||
|
UISettings::values.game_icon_size.SetValue(static_cast<u32>(value));
|
||||||
|
// Update grid view if it's active - update icons in place without recreating model
|
||||||
|
if (list_view->isVisible()) {
|
||||||
|
QAbstractItemModel* current_model = list_view->model();
|
||||||
|
if (current_model && current_model != item_model) {
|
||||||
|
// Update existing filtered model - just update icon sizes and grid size
|
||||||
|
QStandardItemModel* flat_model = qobject_cast<QStandardItemModel*>(current_model);
|
||||||
|
if (flat_model) {
|
||||||
|
const u32 icon_size = static_cast<u32>(value);
|
||||||
|
list_view->setGridSize(QSize(icon_size + 60, icon_size + 80));
|
||||||
|
// Update icon sizes in the existing model by getting original icons from hierarchical model
|
||||||
|
// Store current scroll position to restore it
|
||||||
|
int scroll_position = list_view->verticalScrollBar()->value();
|
||||||
|
QModelIndex current_index = list_view->currentIndex();
|
||||||
|
|
||||||
|
for (int i = 0; i < flat_model->rowCount(); ++i) {
|
||||||
|
QStandardItem* item = flat_model->item(i);
|
||||||
|
if (item) {
|
||||||
|
// Get the original item from hierarchical model to get original icon
|
||||||
|
u64 program_id = item->data(GameListItemPath::ProgramIdRole).toULongLong();
|
||||||
|
|
||||||
|
// Find the original item in hierarchical model
|
||||||
|
QStandardItem* original_item = nullptr;
|
||||||
|
for (int folder_idx = 0; folder_idx < item_model->rowCount(); ++folder_idx) {
|
||||||
|
QStandardItem* folder = item_model->item(folder_idx, 0);
|
||||||
|
if (!folder) continue;
|
||||||
|
for (int game_idx = 0; game_idx < folder->rowCount(); ++game_idx) {
|
||||||
|
QStandardItem* game = folder->child(game_idx, 0);
|
||||||
|
if (game && game->data(GameListItemPath::ProgramIdRole).toULongLong() == program_id) {
|
||||||
|
original_item = game;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (original_item) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (original_item) {
|
||||||
|
// Get original icon from hierarchical model
|
||||||
|
QVariant orig_icon_data = original_item->data(Qt::DecorationRole);
|
||||||
|
if (orig_icon_data.isValid() && orig_icon_data.type() == QVariant::Pixmap) {
|
||||||
|
QPixmap orig_pixmap = orig_icon_data.value<QPixmap>();
|
||||||
|
// Create new rounded icon at new size
|
||||||
|
// Even though original is rounded, we'll scale it and re-apply rounding
|
||||||
|
QPixmap rounded(icon_size, icon_size);
|
||||||
|
rounded.fill(Qt::transparent);
|
||||||
|
|
||||||
|
QPainter painter(&rounded);
|
||||||
|
painter.setRenderHint(QPainter::Antialiasing);
|
||||||
|
|
||||||
|
const int radius = icon_size / 8;
|
||||||
|
QPainterPath path;
|
||||||
|
path.addRoundedRect(0, 0, icon_size, icon_size, radius, radius);
|
||||||
|
painter.setClipPath(path);
|
||||||
|
|
||||||
|
// Scale original pixmap to new size (even if it's already rounded, scaling will work)
|
||||||
|
QPixmap scaled = orig_pixmap.scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
|
||||||
|
painter.drawPixmap(0, 0, scaled);
|
||||||
|
|
||||||
|
item->setData(rounded, Qt::DecorationRole);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore scroll position and selection
|
||||||
|
if (scroll_position >= 0) {
|
||||||
|
list_view->verticalScrollBar()->setValue(scroll_position);
|
||||||
|
}
|
||||||
|
if (current_index.isValid() && current_index.row() < flat_model->rowCount()) {
|
||||||
|
list_view->setCurrentIndex(flat_model->index(current_index.row(), 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No filter active, use PopulateGridView
|
||||||
|
PopulateGridView();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Update title font size in tree view
|
||||||
|
QFont font = tree_view->font();
|
||||||
|
font.setPointSize(qBound(8, value / 8, 24));
|
||||||
|
tree_view->setFont(font);
|
||||||
|
});
|
||||||
|
|
||||||
|
// A-Z sort button - positioned after slider
|
||||||
|
btn_sort_az = new QToolButton(toolbar);
|
||||||
|
UpdateSortButtonIcon();
|
||||||
|
btn_sort_az->setToolTip(tr("Sort by Name"));
|
||||||
|
btn_sort_az->setAutoRaise(true);
|
||||||
|
btn_sort_az->setIconSize(QSize(16, 16));
|
||||||
|
btn_sort_az->setFixedSize(32, 32);
|
||||||
|
btn_sort_az->setStyleSheet(QStringLiteral(
|
||||||
|
"QToolButton {"
|
||||||
|
" border: 1px solid palette(mid);"
|
||||||
|
" border-radius: 4px;"
|
||||||
|
" background: palette(button);"
|
||||||
|
"}"
|
||||||
|
"QToolButton:hover {"
|
||||||
|
" background: palette(light);"
|
||||||
|
"}"
|
||||||
|
));
|
||||||
|
connect(btn_sort_az, &QToolButton::clicked, this, &GameList::ToggleSortOrder);
|
||||||
|
|
||||||
|
// Add widgets to toolbar
|
||||||
|
toolbar_layout->addWidget(btn_list_view);
|
||||||
|
toolbar_layout->addWidget(btn_grid_view);
|
||||||
|
toolbar_layout->addWidget(slider_title_size);
|
||||||
|
toolbar_layout->addWidget(btn_sort_az);
|
||||||
|
toolbar_layout->addStretch(); // Push search to the right
|
||||||
|
toolbar_layout->addWidget(search_field);
|
||||||
|
|
||||||
layout->setContentsMargins(0, 0, 0, 0);
|
layout->setContentsMargins(0, 0, 0, 0);
|
||||||
layout->setSpacing(0);
|
layout->setSpacing(0);
|
||||||
|
layout->addWidget(toolbar);
|
||||||
layout->addWidget(tree_view);
|
layout->addWidget(tree_view);
|
||||||
layout->addWidget(list_view);
|
layout->addWidget(list_view);
|
||||||
layout->addWidget(search_field);
|
|
||||||
setLayout(layout);
|
setLayout(layout);
|
||||||
|
|
||||||
SetViewMode(UISettings::values.game_list_grid_view.GetValue());
|
SetViewMode(UISettings::values.game_list_grid_view.GetValue());
|
||||||
@@ -570,8 +859,14 @@ void GameList::DonePopulating(const QStringList& watch_list) {
|
|||||||
}
|
}
|
||||||
item_model->sort(tree_view->header()->sortIndicatorSection(), tree_view->header()->sortIndicatorOrder());
|
item_model->sort(tree_view->header()->sortIndicatorSection(), tree_view->header()->sortIndicatorOrder());
|
||||||
if (list_view->isVisible()) {
|
if (list_view->isVisible()) {
|
||||||
|
// Preserve filter when repopulating
|
||||||
|
QString filter_text = search_field->filterText();
|
||||||
|
if (!filter_text.isEmpty()) {
|
||||||
|
FilterGridView(filter_text);
|
||||||
|
} else {
|
||||||
PopulateGridView();
|
PopulateGridView();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
emit PopulatingCompleted();
|
emit PopulatingCompleted();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -865,8 +1160,14 @@ const QStringList GameList::supported_file_extensions = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (list_view->isVisible()) {
|
if (list_view->isVisible()) {
|
||||||
|
// Preserve filter when updating favorites
|
||||||
|
QString filter_text = search_field->filterText();
|
||||||
|
if (!filter_text.isEmpty()) {
|
||||||
|
FilterGridView(filter_text);
|
||||||
|
} else {
|
||||||
PopulateGridView();
|
PopulateGridView();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
SaveConfig();
|
SaveConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -941,7 +1242,13 @@ const QStringList GameList::supported_file_extensions = {
|
|||||||
|
|
||||||
void GameList::SetViewMode(bool grid_view) {
|
void GameList::SetViewMode(bool grid_view) {
|
||||||
if (grid_view) {
|
if (grid_view) {
|
||||||
|
// Check if there's an active filter - if so, use FilterGridView instead
|
||||||
|
QString filter_text = search_field->filterText();
|
||||||
|
if (!filter_text.isEmpty()) {
|
||||||
|
FilterGridView(filter_text);
|
||||||
|
} else {
|
||||||
PopulateGridView();
|
PopulateGridView();
|
||||||
|
}
|
||||||
tree_view->setVisible(false);
|
tree_view->setVisible(false);
|
||||||
list_view->setVisible(true);
|
list_view->setVisible(true);
|
||||||
if (list_view->model() && list_view->model()->rowCount() > 0) {
|
if (list_view->model() && list_view->model()->rowCount() > 0) {
|
||||||
@@ -954,6 +1261,11 @@ const QStringList GameList::supported_file_extensions = {
|
|||||||
tree_view->setCurrentIndex(item_model->index(0, 0));
|
tree_view->setCurrentIndex(item_model->index(0, 0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Update button states
|
||||||
|
if (btn_list_view && btn_grid_view) {
|
||||||
|
btn_list_view->setChecked(!grid_view);
|
||||||
|
btn_grid_view->setChecked(grid_view);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameList::PopulateGridView() {
|
void GameList::PopulateGridView() {
|
||||||
@@ -964,6 +1276,7 @@ const QStringList GameList::supported_file_extensions = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
QStandardItemModel* flat_model = new QStandardItemModel(this);
|
QStandardItemModel* flat_model = new QStandardItemModel(this);
|
||||||
|
flat_model->setSortRole(GameListItemPath::SortRole);
|
||||||
for (int i = 0; i < hierarchical_model->rowCount(); ++i) {
|
for (int i = 0; i < hierarchical_model->rowCount(); ++i) {
|
||||||
QStandardItem* folder = hierarchical_model->item(i, 0);
|
QStandardItem* folder = hierarchical_model->item(i, 0);
|
||||||
if (!folder) continue;
|
if (!folder) continue;
|
||||||
@@ -991,10 +1304,108 @@ const QStringList GameList::supported_file_extensions = {
|
|||||||
list_view->setModel(flat_model);
|
list_view->setModel(flat_model);
|
||||||
const u32 icon_size = UISettings::values.game_icon_size.GetValue();
|
const u32 icon_size = UISettings::values.game_icon_size.GetValue();
|
||||||
list_view->setGridSize(QSize(icon_size + 60, icon_size + 80));
|
list_view->setGridSize(QSize(icon_size + 60, icon_size + 80));
|
||||||
|
// Sort the grid view using current sort order
|
||||||
|
flat_model->sort(0, current_sort_order);
|
||||||
|
// Update icon sizes in the model - ensure all icons are consistently sized with rounded corners
|
||||||
|
for (int i = 0; i < flat_model->rowCount(); ++i) {
|
||||||
|
QStandardItem* item = flat_model->item(i);
|
||||||
|
if (item) {
|
||||||
|
QVariant icon_data = item->data(Qt::DecorationRole);
|
||||||
|
if (icon_data.isValid() && icon_data.type() == QVariant::Pixmap) {
|
||||||
|
QPixmap pixmap = icon_data.value<QPixmap>();
|
||||||
|
if (!pixmap.isNull()) {
|
||||||
|
// Always recreate the rounded icon at the exact target size for consistency
|
||||||
|
// This ensures all icons are the same size regardless of their original size
|
||||||
|
QPixmap rounded(icon_size, icon_size);
|
||||||
|
rounded.fill(Qt::transparent);
|
||||||
|
|
||||||
|
QPainter painter(&rounded);
|
||||||
|
painter.setRenderHint(QPainter::Antialiasing);
|
||||||
|
|
||||||
|
// Create rounded rectangle clipping path
|
||||||
|
const int radius = icon_size / 8;
|
||||||
|
QPainterPath path;
|
||||||
|
path.addRoundedRect(0, 0, icon_size, icon_size, radius, radius);
|
||||||
|
painter.setClipPath(path);
|
||||||
|
|
||||||
|
// Scale the source pixmap to fill the icon size exactly
|
||||||
|
QPixmap scaled = pixmap.scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
|
||||||
|
painter.drawPixmap(0, 0, scaled);
|
||||||
|
|
||||||
|
item->setData(rounded, Qt::DecorationRole);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameList::ToggleViewMode() {
|
void GameList::ToggleViewMode() {
|
||||||
bool current_grid_view = UISettings::values.game_list_grid_view.GetValue();
|
bool current_grid_view = UISettings::values.game_list_grid_view.GetValue();
|
||||||
UISettings::values.game_list_grid_view.SetValue(!current_grid_view);
|
UISettings::values.game_list_grid_view.SetValue(!current_grid_view);
|
||||||
SetViewMode(!current_grid_view);
|
SetViewMode(!current_grid_view);
|
||||||
|
// Button states are updated in SetViewMode
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameList::SortAlphabetically() {
|
||||||
|
if (tree_view->isVisible()) {
|
||||||
|
// Sort tree view by name column using current sort order
|
||||||
|
tree_view->header()->setSortIndicator(COLUMN_NAME, current_sort_order);
|
||||||
|
item_model->sort(COLUMN_NAME, current_sort_order);
|
||||||
|
} else if (list_view->isVisible()) {
|
||||||
|
// Sort grid view alphabetically using current sort order
|
||||||
|
QAbstractItemModel* current_model = list_view->model();
|
||||||
|
if (current_model && current_model != item_model) {
|
||||||
|
// Sort the flat model used by list view (filtered or unfiltered)
|
||||||
|
QStandardItemModel* flat_model = qobject_cast<QStandardItemModel*>(current_model);
|
||||||
|
if (flat_model) {
|
||||||
|
// Use SortRole for proper alphabetical sorting
|
||||||
|
flat_model->setSortRole(GameListItemPath::SortRole);
|
||||||
|
flat_model->sort(0, current_sort_order);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If using item_model directly, repopulate grid view to apply sort
|
||||||
|
// Preserve filter if active
|
||||||
|
QString filter_text = search_field->filterText();
|
||||||
|
if (!filter_text.isEmpty()) {
|
||||||
|
FilterGridView(filter_text);
|
||||||
|
} else {
|
||||||
|
PopulateGridView();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UpdateSortButtonIcon();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameList::ToggleSortOrder() {
|
||||||
|
// Toggle between ascending and descending, just like clicking the Name column header
|
||||||
|
current_sort_order = (current_sort_order == Qt::AscendingOrder)
|
||||||
|
? Qt::DescendingOrder
|
||||||
|
: Qt::AscendingOrder;
|
||||||
|
SortAlphabetically();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameList::UpdateSortButtonIcon() {
|
||||||
|
if (!btn_sort_az) return;
|
||||||
|
|
||||||
|
QIcon sort_icon;
|
||||||
|
if (current_sort_order == Qt::AscendingOrder) {
|
||||||
|
// Ascending (A-Z) - arrow up
|
||||||
|
sort_icon = QIcon::fromTheme(QStringLiteral("view-sort-ascending"));
|
||||||
|
if (sort_icon.isNull()) {
|
||||||
|
sort_icon = QIcon::fromTheme(QStringLiteral("sort-ascending"));
|
||||||
|
}
|
||||||
|
if (sort_icon.isNull()) {
|
||||||
|
sort_icon = style()->standardIcon(QStyle::SP_ArrowUp);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Descending (Z-A) - arrow down
|
||||||
|
sort_icon = QIcon::fromTheme(QStringLiteral("view-sort-descending"));
|
||||||
|
if (sort_icon.isNull()) {
|
||||||
|
sort_icon = QIcon::fromTheme(QStringLiteral("sort-descending"));
|
||||||
|
}
|
||||||
|
if (sort_icon.isNull()) {
|
||||||
|
sort_icon = style()->standardIcon(QStyle::SP_ArrowDown);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
btn_sort_az->setIcon(sort_icon);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,13 +7,17 @@
|
|||||||
#include <map>
|
#include <map>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <QFileSystemWatcher>
|
#include <QFileSystemWatcher>
|
||||||
|
#include <QHBoxLayout>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QLineEdit>
|
#include <QLineEdit>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QListView>
|
#include <QListView>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QSlider>
|
||||||
#include <QStandardItemModel>
|
#include <QStandardItemModel>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
#include <QToolButton>
|
||||||
#include <QTreeView>
|
#include <QTreeView>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
#include <QVector>
|
#include <QVector>
|
||||||
@@ -103,6 +107,8 @@ public:
|
|||||||
|
|
||||||
void SetViewMode(bool grid_view);
|
void SetViewMode(bool grid_view);
|
||||||
void ToggleViewMode();
|
void ToggleViewMode();
|
||||||
|
void SortAlphabetically();
|
||||||
|
void ToggleSortOrder();
|
||||||
|
|
||||||
QStandardItemModel* GetModel() const;
|
QStandardItemModel* GetModel() const;
|
||||||
|
|
||||||
@@ -173,12 +179,20 @@ private:
|
|||||||
|
|
||||||
void changeEvent(QEvent*) override;
|
void changeEvent(QEvent*) override;
|
||||||
void RetranslateUI();
|
void RetranslateUI();
|
||||||
|
void UpdateSortButtonIcon();
|
||||||
|
|
||||||
std::shared_ptr<FileSys::VfsFilesystem> vfs;
|
std::shared_ptr<FileSys::VfsFilesystem> vfs;
|
||||||
FileSys::ManualContentProvider* provider;
|
FileSys::ManualContentProvider* provider;
|
||||||
GameListSearchField* search_field;
|
GameListSearchField* search_field;
|
||||||
GMainWindow* main_window = nullptr;
|
GMainWindow* main_window = nullptr;
|
||||||
QVBoxLayout* layout = nullptr;
|
QVBoxLayout* layout = nullptr;
|
||||||
|
QWidget* toolbar = nullptr;
|
||||||
|
QHBoxLayout* toolbar_layout = nullptr;
|
||||||
|
QToolButton* btn_list_view = nullptr;
|
||||||
|
QToolButton* btn_grid_view = nullptr;
|
||||||
|
QSlider* slider_title_size = nullptr;
|
||||||
|
QToolButton* btn_sort_az = nullptr;
|
||||||
|
Qt::SortOrder current_sort_order = Qt::AscendingOrder;
|
||||||
QTreeView* tree_view = nullptr;
|
QTreeView* tree_view = nullptr;
|
||||||
QListView* list_view = nullptr;
|
QListView* list_view = nullptr;
|
||||||
QStandardItemModel* item_model = nullptr;
|
QStandardItemModel* item_model = nullptr;
|
||||||
|
|||||||
Reference in New Issue
Block a user