mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2024-11-23 06:15:38 +00:00
Qt: Implement game grid/cover view
This commit is contained in:
parent
b193374dd4
commit
43b0d84a1d
|
@ -2,9 +2,67 @@
|
|||
#include "common/string_util.h"
|
||||
#include "core/system.h"
|
||||
#include <QtGui/QIcon>
|
||||
#include <QtGui/QPainter>
|
||||
|
||||
static constexpr std::array<const char*, GameListModel::Column_Count> s_column_names = {
|
||||
{"Type", "Code", "Title", "File Title", "Size", "Region", "Compatibility"}};
|
||||
{"Type", "Code", "Title", "File Title", "Size", "Region", "Compatibility", "Cover"}};
|
||||
|
||||
static constexpr int COVER_ART_WIDTH = 512;
|
||||
static constexpr int COVER_ART_HEIGHT = 512;
|
||||
static constexpr int COVER_ART_SPACING = 32;
|
||||
|
||||
static void resizeAndPadPixmap(QPixmap* pm, int expected_width, int expected_height)
|
||||
{
|
||||
if (pm->width() == expected_width && pm->height() == expected_height)
|
||||
return;
|
||||
|
||||
*pm = pm->scaled(expected_width, expected_height, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
if (pm->width() == expected_width && pm->height() == expected_height)
|
||||
return;
|
||||
|
||||
int xoffs = 0;
|
||||
int yoffs = 0;
|
||||
if (pm->width() < expected_width)
|
||||
xoffs = (expected_width - pm->width()) / 2;
|
||||
if (pm->height() < expected_height)
|
||||
yoffs = (expected_height - pm->height()) / 2;
|
||||
|
||||
QPixmap padded_image(expected_width, expected_height);
|
||||
padded_image.fill(Qt::transparent);
|
||||
QPainter painter;
|
||||
if (painter.begin(&padded_image))
|
||||
{
|
||||
painter.setCompositionMode(QPainter::CompositionMode_Source);
|
||||
painter.drawPixmap(xoffs, yoffs, *pm);
|
||||
painter.setCompositionMode(QPainter::CompositionMode_Destination);
|
||||
painter.fillRect(padded_image.rect(), QColor(0, 0, 0, 0));
|
||||
painter.end();
|
||||
}
|
||||
|
||||
*pm = padded_image;
|
||||
}
|
||||
|
||||
static QPixmap createPlaceholderImage(int width, int height, float scale, const std::string& title)
|
||||
{
|
||||
QPixmap pm(QStringLiteral(":/icons/cover-placeholder.png"));
|
||||
if (pm.isNull())
|
||||
return QPixmap(width, height);
|
||||
|
||||
resizeAndPadPixmap(&pm, width, height);
|
||||
QPainter painter;
|
||||
if (painter.begin(&pm))
|
||||
{
|
||||
QFont font;
|
||||
font.setPointSize(std::max(static_cast<int>(32.0f * scale), 1));
|
||||
painter.setFont(font);
|
||||
painter.setPen(Qt::white);
|
||||
|
||||
painter.drawText(QRect(0, 0, width, height), Qt::AlignCenter | Qt::TextWordWrap, QString::fromStdString(title));
|
||||
painter.end();
|
||||
}
|
||||
|
||||
return pm;
|
||||
}
|
||||
|
||||
std::optional<GameListModel::Column> GameListModel::getColumnIdForName(std::string_view name)
|
||||
{
|
||||
|
@ -30,6 +88,30 @@ GameListModel::GameListModel(GameList* game_list, QObject* parent /* = nullptr *
|
|||
}
|
||||
GameListModel::~GameListModel() = default;
|
||||
|
||||
void GameListModel::setCoverScale(float scale)
|
||||
{
|
||||
if (m_cover_scale == scale)
|
||||
return;
|
||||
|
||||
m_cover_pixmap_cache.clear();
|
||||
m_cover_scale = scale;
|
||||
}
|
||||
|
||||
int GameListModel::getCoverArtWidth() 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);
|
||||
}
|
||||
|
||||
int GameListModel::getCoverArtSpacing() const
|
||||
{
|
||||
return std::max(static_cast<int>(static_cast<float>(COVER_ART_SPACING) * m_cover_scale), 1);
|
||||
}
|
||||
|
||||
int GameListModel::rowCount(const QModelIndex& parent) const
|
||||
{
|
||||
if (parent.isValid())
|
||||
|
@ -78,6 +160,14 @@ QVariant GameListModel::data(const QModelIndex& index, int role) const
|
|||
case Column_Size:
|
||||
return QString("%1 MB").arg(static_cast<double>(ge.total_size) / 1048576.0, 0, 'f', 2);
|
||||
|
||||
case Column_Cover:
|
||||
{
|
||||
if (m_show_titles_for_covers)
|
||||
return QString::fromStdString(ge.title);
|
||||
else
|
||||
return {};
|
||||
}
|
||||
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
|
@ -94,6 +184,7 @@ QVariant GameListModel::data(const QModelIndex& index, int role) const
|
|||
return QString::fromStdString(ge.code);
|
||||
|
||||
case Column_Title:
|
||||
case Column_Cover:
|
||||
return QString::fromStdString(ge.title);
|
||||
|
||||
case Column_FileTitle:
|
||||
|
@ -155,6 +246,29 @@ QVariant GameListModel::data(const QModelIndex& index, int role) const
|
|||
ge.compatibility_rating)];
|
||||
}
|
||||
|
||||
case Column_Cover:
|
||||
{
|
||||
auto it = m_cover_pixmap_cache.find(ge.path);
|
||||
if (it != m_cover_pixmap_cache.end())
|
||||
return it->second;
|
||||
|
||||
QPixmap image;
|
||||
std::string path = m_game_list->GetCoverImagePathForEntry(&ge);
|
||||
if (!path.empty())
|
||||
{
|
||||
image = QPixmap(QString::fromStdString(path));
|
||||
if (!image.isNull())
|
||||
resizeAndPadPixmap(&image, getCoverArtWidth(), getCoverArtHeight());
|
||||
}
|
||||
|
||||
if (image.isNull())
|
||||
image = createPlaceholderImage(getCoverArtWidth(), getCoverArtHeight(), m_cover_scale, ge.title);
|
||||
|
||||
m_cover_pixmap_cache.emplace(ge.path, image);
|
||||
return image;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
|
|
|
@ -3,8 +3,10 @@
|
|||
#include "frontend-common/game_list.h"
|
||||
#include <QtCore/QAbstractTableModel>
|
||||
#include <QtGui/QPixmap>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
|
||||
class GameListModel final : public QAbstractTableModel
|
||||
{
|
||||
|
@ -20,6 +22,7 @@ public:
|
|||
Column_Size,
|
||||
Column_Region,
|
||||
Column_Compatibility,
|
||||
Column_Cover,
|
||||
|
||||
Column_Count
|
||||
};
|
||||
|
@ -43,11 +46,22 @@ public:
|
|||
|
||||
bool lessThan(const QModelIndex& left_index, const QModelIndex& right_index, int column, bool ascending) const;
|
||||
|
||||
bool getShowCoverTitles() const { return m_show_titles_for_covers; }
|
||||
void setShowCoverTitles(bool enabled) { m_show_titles_for_covers = enabled; }
|
||||
|
||||
float getCoverScale() const { return m_cover_scale; }
|
||||
void setCoverScale(float scale);
|
||||
int getCoverArtWidth() const;
|
||||
int getCoverArtHeight() const;
|
||||
int getCoverArtSpacing() const;
|
||||
|
||||
private:
|
||||
void loadCommonImages();
|
||||
void setColumnDisplayNames();
|
||||
|
||||
GameList* m_game_list;
|
||||
float m_cover_scale = 1.0f;
|
||||
bool m_show_titles_for_covers = false;
|
||||
|
||||
std::array<QString, Column_Count> m_column_display_names;
|
||||
|
||||
|
@ -60,4 +74,5 @@ private:
|
|||
QPixmap m_region_us_pixmap;
|
||||
|
||||
std::array<QPixmap, static_cast<int>(GameListCompatibilityRating::Count)> m_compatibiliy_pixmaps;
|
||||
mutable std::unordered_map<std::string, QPixmap> m_cover_pixmap_cache;
|
||||
};
|
|
@ -7,6 +7,7 @@
|
|||
#include "qtutils.h"
|
||||
#include <QtCore/QSortFilterProxyModel>
|
||||
#include <QtGui/QPixmap>
|
||||
#include <QtGui/QWheelEvent>
|
||||
#include <QtWidgets/QHeaderView>
|
||||
#include <QtWidgets/QMenu>
|
||||
|
||||
|
@ -42,11 +43,14 @@ void GameListWidget::initialize(QtHostInterface* host_interface)
|
|||
|
||||
connect(m_host_interface, &QtHostInterface::gameListRefreshed, this, &GameListWidget::onGameListRefreshed);
|
||||
|
||||
m_table_model = new GameListModel(m_game_list, this);
|
||||
m_table_sort_model = new GameListSortModel(m_table_model);
|
||||
m_table_sort_model->setSourceModel(m_table_model);
|
||||
m_model = new GameListModel(m_game_list, this);
|
||||
m_model->setCoverScale(host_interface->GetFloatSettingValue("UI", "GameListCoverArtScale", 0.45f));
|
||||
m_model->setShowCoverTitles(host_interface->GetBoolSettingValue("UI", "GameListShowCoverTitles", true));
|
||||
|
||||
m_sort_model = new GameListSortModel(m_model);
|
||||
m_sort_model->setSourceModel(m_model);
|
||||
m_table_view = new QTableView(this);
|
||||
m_table_view->setModel(m_table_sort_model);
|
||||
m_table_view->setModel(m_sort_model);
|
||||
m_table_view->setSortingEnabled(true);
|
||||
m_table_view->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
m_table_view->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
|
@ -73,19 +77,59 @@ void GameListWidget::initialize(QtHostInterface* host_interface)
|
|||
&GameListWidget::onTableViewHeaderSortIndicatorChanged);
|
||||
|
||||
insertWidget(0, m_table_view);
|
||||
setCurrentIndex(0);
|
||||
|
||||
m_list_view = new GameListGridListView(this);
|
||||
m_list_view->setModel(m_sort_model);
|
||||
m_list_view->setModelColumn(GameListModel::Column_Cover);
|
||||
m_list_view->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||
m_list_view->setViewMode(QListView::IconMode);
|
||||
m_list_view->setResizeMode(QListView::Adjust);
|
||||
m_list_view->setUniformItemSizes(true);
|
||||
m_list_view->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
m_list_view->setFrameStyle(QFrame::NoFrame);
|
||||
m_list_view->setSpacing(m_model->getCoverArtSpacing());
|
||||
updateListFont();
|
||||
|
||||
connect(m_list_view->selectionModel(), &QItemSelectionModel::currentChanged, this,
|
||||
&GameListWidget::onSelectionModelCurrentChanged);
|
||||
connect(m_list_view, &GameListGridListView::zoomIn, this, &GameListWidget::listZoomIn);
|
||||
connect(m_list_view, &GameListGridListView::zoomOut, this, &GameListWidget::listZoomOut);
|
||||
connect(m_list_view, &QListView::doubleClicked, this, &GameListWidget::onListViewItemDoubleClicked);
|
||||
connect(m_list_view, &QListView::customContextMenuRequested, this, &GameListWidget::onListViewContextMenuRequested);
|
||||
|
||||
insertWidget(1, m_list_view);
|
||||
|
||||
if (m_host_interface->GetBoolSettingValue("UI", "GameListGridView", false))
|
||||
setCurrentIndex(1);
|
||||
else
|
||||
setCurrentIndex(0);
|
||||
|
||||
resizeTableViewColumnsToFit();
|
||||
}
|
||||
|
||||
bool GameListWidget::isShowingGameList() const
|
||||
{
|
||||
return currentIndex() == 0;
|
||||
}
|
||||
|
||||
bool GameListWidget::isShowingGameGrid() const
|
||||
{
|
||||
return currentIndex() == 1;
|
||||
}
|
||||
|
||||
bool GameListWidget::getShowGridCoverTitles() const
|
||||
{
|
||||
return m_model->getShowCoverTitles();
|
||||
}
|
||||
|
||||
void GameListWidget::onGameListRefreshed()
|
||||
{
|
||||
m_table_model->refresh();
|
||||
m_model->refresh();
|
||||
}
|
||||
|
||||
void GameListWidget::onSelectionModelCurrentChanged(const QModelIndex& current, const QModelIndex& previous)
|
||||
{
|
||||
const QModelIndex source_index = m_table_sort_model->mapToSource(current);
|
||||
const QModelIndex source_index = m_sort_model->mapToSource(current);
|
||||
if (!source_index.isValid() || source_index.row() >= static_cast<int>(m_game_list->GetEntryCount()))
|
||||
{
|
||||
emit entrySelected(nullptr);
|
||||
|
@ -98,7 +142,7 @@ void GameListWidget::onSelectionModelCurrentChanged(const QModelIndex& current,
|
|||
|
||||
void GameListWidget::onTableViewItemDoubleClicked(const QModelIndex& index)
|
||||
{
|
||||
const QModelIndex source_index = m_table_sort_model->mapToSource(index);
|
||||
const QModelIndex source_index = m_sort_model->mapToSource(index);
|
||||
if (!source_index.isValid() || source_index.row() >= static_cast<int>(m_game_list->GetEntryCount()))
|
||||
return;
|
||||
|
||||
|
@ -115,13 +159,35 @@ void GameListWidget::onTableViewContextMenuRequested(const QPoint& point)
|
|||
emit entryContextMenuRequested(m_table_view->mapToGlobal(point), entry);
|
||||
}
|
||||
|
||||
void GameListWidget::onListViewItemDoubleClicked(const QModelIndex& index)
|
||||
{
|
||||
const QModelIndex source_index = m_sort_model->mapToSource(index);
|
||||
if (!source_index.isValid() || source_index.row() >= static_cast<int>(m_game_list->GetEntryCount()))
|
||||
return;
|
||||
|
||||
const GameListEntry& entry = m_game_list->GetEntries().at(source_index.row());
|
||||
emit entryDoubleClicked(&entry);
|
||||
}
|
||||
|
||||
void GameListWidget::onListViewContextMenuRequested(const QPoint& point)
|
||||
{
|
||||
const GameListEntry* entry = getSelectedEntry();
|
||||
if (!entry)
|
||||
return;
|
||||
|
||||
emit entryContextMenuRequested(m_list_view->mapToGlobal(point), entry);
|
||||
}
|
||||
|
||||
void GameListWidget::onTableViewHeaderContextMenuRequested(const QPoint& point)
|
||||
{
|
||||
QMenu menu;
|
||||
|
||||
for (int column = 0; column < GameListModel::Column_Count; column++)
|
||||
{
|
||||
QAction* action = menu.addAction(m_table_model->getColumnDisplayName(column));
|
||||
if (column == GameListModel::Column_Cover)
|
||||
continue;
|
||||
|
||||
QAction* action = menu.addAction(m_model->getColumnDisplayName(column));
|
||||
action->setCheckable(true);
|
||||
action->setChecked(!m_table_view->isColumnHidden(column));
|
||||
connect(action, &QAction::toggled, [this, column](bool enabled) {
|
||||
|
@ -139,6 +205,66 @@ void GameListWidget::onTableViewHeaderSortIndicatorChanged(int, Qt::SortOrder)
|
|||
saveTableViewColumnSortSettings();
|
||||
}
|
||||
|
||||
void GameListWidget::listZoom(float delta)
|
||||
{
|
||||
static constexpr float MIN_SCALE = 0.1f;
|
||||
static constexpr float MAX_SCALE = 2.0f;
|
||||
|
||||
const float new_scale = std::clamp(m_model->getCoverScale() + delta, MIN_SCALE, MAX_SCALE);
|
||||
m_host_interface->SetFloatSettingValue("UI", "GameListCoverArtScale", new_scale);
|
||||
m_model->setCoverScale(new_scale);
|
||||
updateListFont();
|
||||
|
||||
m_model->refresh();
|
||||
}
|
||||
|
||||
void GameListWidget::listZoomIn()
|
||||
{
|
||||
listZoom(0.05f);
|
||||
}
|
||||
|
||||
void GameListWidget::listZoomOut()
|
||||
{
|
||||
listZoom(-0.05f);
|
||||
}
|
||||
|
||||
void GameListWidget::showGameList()
|
||||
{
|
||||
if (currentIndex() == 0)
|
||||
return;
|
||||
|
||||
m_host_interface->SetBoolSettingValue("UI", "GameListGridView", false);
|
||||
setCurrentIndex(0);
|
||||
resizeTableViewColumnsToFit();
|
||||
}
|
||||
|
||||
void GameListWidget::showGameGrid()
|
||||
{
|
||||
if (currentIndex() == 1)
|
||||
return;
|
||||
|
||||
m_host_interface->SetBoolSettingValue("UI", "GameListGridView", true);
|
||||
setCurrentIndex(1);
|
||||
}
|
||||
|
||||
void GameListWidget::setShowCoverTitles(bool enabled)
|
||||
{
|
||||
if (m_model->getShowCoverTitles() == enabled)
|
||||
return;
|
||||
|
||||
m_host_interface->SetBoolSettingValue("UI", "GameListShowCoverTitles", enabled);
|
||||
m_model->setShowCoverTitles(enabled);
|
||||
if (isShowingGameGrid())
|
||||
m_model->refresh();
|
||||
}
|
||||
|
||||
void GameListWidget::updateListFont()
|
||||
{
|
||||
QFont font;
|
||||
font.setPointSizeF(16.0f * m_model->getCoverScale());
|
||||
m_list_view->setFont(font);
|
||||
}
|
||||
|
||||
void GameListWidget::resizeEvent(QResizeEvent* event)
|
||||
{
|
||||
QStackedWidget::resizeEvent(event);
|
||||
|
@ -193,7 +319,7 @@ void GameListWidget::loadTableViewColumnSortSettings()
|
|||
.value_or(DEFAULT_SORT_COLUMN);
|
||||
const bool sort_descending =
|
||||
m_host_interface->GetBoolSettingValue("GameListTableView", "SortDescending", DEFAULT_SORT_DESCENDING);
|
||||
m_table_sort_model->sort(sort_column, sort_descending ? Qt::DescendingOrder : Qt::AscendingOrder);
|
||||
m_sort_model->sort(sort_column, sort_descending ? Qt::DescendingOrder : Qt::AscendingOrder);
|
||||
}
|
||||
|
||||
void GameListWidget::saveTableViewColumnSortSettings()
|
||||
|
@ -212,17 +338,53 @@ void GameListWidget::saveTableViewColumnSortSettings()
|
|||
|
||||
const GameListEntry* GameListWidget::getSelectedEntry() const
|
||||
{
|
||||
const QItemSelectionModel* selection_model = m_table_view->selectionModel();
|
||||
if (!selection_model->hasSelection())
|
||||
return nullptr;
|
||||
if (currentIndex() == 0)
|
||||
{
|
||||
const QItemSelectionModel* selection_model = m_table_view->selectionModel();
|
||||
if (!selection_model->hasSelection())
|
||||
return nullptr;
|
||||
|
||||
const QModelIndexList selected_rows = selection_model->selectedRows();
|
||||
if (selected_rows.empty())
|
||||
return nullptr;
|
||||
const QModelIndexList selected_rows = selection_model->selectedRows();
|
||||
if (selected_rows.empty())
|
||||
return nullptr;
|
||||
|
||||
const QModelIndex source_index = m_table_sort_model->mapToSource(selected_rows[0]);
|
||||
if (!source_index.isValid() || source_index.row() >= static_cast<int>(m_game_list->GetEntryCount()))
|
||||
return nullptr;
|
||||
const QModelIndex source_index = m_sort_model->mapToSource(selected_rows[0]);
|
||||
if (!source_index.isValid() || source_index.row() >= static_cast<int>(m_game_list->GetEntryCount()))
|
||||
return nullptr;
|
||||
|
||||
return &m_game_list->GetEntries().at(source_index.row());
|
||||
return &m_game_list->GetEntries().at(source_index.row());
|
||||
}
|
||||
else
|
||||
{
|
||||
const QItemSelectionModel* selection_model = m_list_view->selectionModel();
|
||||
if (!selection_model->hasSelection())
|
||||
return nullptr;
|
||||
|
||||
const QModelIndex source_index = m_sort_model->mapToSource(selection_model->currentIndex());
|
||||
if (!source_index.isValid() || source_index.row() >= static_cast<int>(m_game_list->GetEntryCount()))
|
||||
return nullptr;
|
||||
|
||||
return &m_game_list->GetEntries().at(source_index.row());
|
||||
}
|
||||
}
|
||||
|
||||
GameListGridListView::GameListGridListView(QWidget* parent /*= nullptr*/) : QListView(parent) {}
|
||||
|
||||
void GameListGridListView::wheelEvent(QWheelEvent* e)
|
||||
{
|
||||
if (e->modifiers() & Qt::ControlModifier)
|
||||
{
|
||||
int dy = e->angleDelta().y();
|
||||
if (dy != 0)
|
||||
{
|
||||
if (dy < 0)
|
||||
zoomOut();
|
||||
else
|
||||
zoomIn();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
QListView::wheelEvent(e);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#pragma once
|
||||
#include <QtWidgets/QListView>
|
||||
#include <QtWidgets/QStackedWidget>
|
||||
#include <QtWidgets/QTableView>
|
||||
|
||||
|
@ -10,6 +11,21 @@ class GameListSortModel;
|
|||
|
||||
class QtHostInterface;
|
||||
|
||||
class GameListGridListView : public QListView
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
GameListGridListView(QWidget* parent = nullptr);
|
||||
|
||||
Q_SIGNALS:
|
||||
void zoomOut();
|
||||
void zoomIn();
|
||||
|
||||
protected:
|
||||
void wheelEvent(QWheelEvent* e);
|
||||
};
|
||||
|
||||
class GameListWidget : public QStackedWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
@ -20,6 +36,11 @@ public:
|
|||
|
||||
void initialize(QtHostInterface* host_interface);
|
||||
|
||||
bool isShowingGameList() const;
|
||||
bool isShowingGameGrid() const;
|
||||
|
||||
bool getShowGridCoverTitles() const;
|
||||
|
||||
Q_SIGNALS:
|
||||
void entrySelected(const GameListEntry* entry);
|
||||
void entryDoubleClicked(const GameListEntry* entry);
|
||||
|
@ -32,6 +53,15 @@ private Q_SLOTS:
|
|||
void onTableViewContextMenuRequested(const QPoint& point);
|
||||
void onTableViewHeaderContextMenuRequested(const QPoint& point);
|
||||
void onTableViewHeaderSortIndicatorChanged(int, Qt::SortOrder);
|
||||
void onListViewItemDoubleClicked(const QModelIndex& index);
|
||||
void onListViewContextMenuRequested(const QPoint& point);
|
||||
|
||||
public Q_SLOTS:
|
||||
void showGameList();
|
||||
void showGameGrid();
|
||||
void setShowCoverTitles(bool enabled);
|
||||
void listZoomIn();
|
||||
void listZoomOut();
|
||||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent* event);
|
||||
|
@ -44,11 +74,14 @@ private:
|
|||
void saveTableViewColumnVisibilitySettings(int column);
|
||||
void loadTableViewColumnSortSettings();
|
||||
void saveTableViewColumnSortSettings();
|
||||
void listZoom(float delta);
|
||||
void updateListFont();
|
||||
|
||||
QtHostInterface* m_host_interface = nullptr;
|
||||
GameList* m_game_list = nullptr;
|
||||
|
||||
GameListModel* m_table_model = nullptr;
|
||||
GameListSortModel* m_table_sort_model = nullptr;
|
||||
GameListModel* m_model = nullptr;
|
||||
GameListSortModel* m_sort_model = nullptr;
|
||||
QTableView* m_table_view = nullptr;
|
||||
GameListGridListView* m_list_view = nullptr;
|
||||
};
|
||||
|
|
|
@ -335,6 +335,15 @@ void MainWindow::onViewGameListActionTriggered()
|
|||
if (m_emulation_running)
|
||||
m_host_interface->pauseSystem(true);
|
||||
switchToGameListView();
|
||||
m_game_list_widget->showGameList();
|
||||
}
|
||||
|
||||
void MainWindow::onViewGameGridActionTriggered()
|
||||
{
|
||||
if (m_emulation_running)
|
||||
m_host_interface->pauseSystem(true);
|
||||
switchToGameListView();
|
||||
m_game_list_widget->showGameGrid();
|
||||
}
|
||||
|
||||
void MainWindow::onViewSystemDisplayTriggered()
|
||||
|
@ -490,6 +499,8 @@ void MainWindow::setupAdditionalUi()
|
|||
m_status_frame_time_widget->setFixedSize(190, 16);
|
||||
m_status_frame_time_widget->hide();
|
||||
|
||||
m_ui.actionGridViewShowTitles->setChecked(m_game_list_widget->getShowGridCoverTitles());
|
||||
|
||||
updateDebugMenuVisibility();
|
||||
|
||||
for (u32 i = 0; i < static_cast<u32>(CPUExecutionMode::Count); i++)
|
||||
|
@ -597,6 +608,11 @@ void MainWindow::updateEmulationActions(bool starting, bool running)
|
|||
m_ui.statusBar->clearMessage();
|
||||
}
|
||||
|
||||
bool MainWindow::isShowingGameList() const
|
||||
{
|
||||
return m_ui.mainContainer->currentIndex() == 0;
|
||||
}
|
||||
|
||||
void MainWindow::switchToGameListView()
|
||||
{
|
||||
m_ui.mainContainer->setCurrentIndex(0);
|
||||
|
@ -671,6 +687,7 @@ void MainWindow::connectSignals()
|
|||
connect(m_ui.actionViewToolbar, &QAction::toggled, this, &MainWindow::onViewToolbarActionToggled);
|
||||
connect(m_ui.actionViewStatusBar, &QAction::toggled, this, &MainWindow::onViewStatusBarActionToggled);
|
||||
connect(m_ui.actionViewGameList, &QAction::triggered, this, &MainWindow::onViewGameListActionTriggered);
|
||||
connect(m_ui.actionViewGameGrid, &QAction::triggered, this, &MainWindow::onViewGameGridActionTriggered);
|
||||
connect(m_ui.actionViewSystemDisplay, &QAction::triggered, this, &MainWindow::onViewSystemDisplayTriggered);
|
||||
connect(m_ui.actionGitHubRepository, &QAction::triggered, this, &MainWindow::onGitHubRepositoryActionTriggered);
|
||||
connect(m_ui.actionIssueTracker, &QAction::triggered, this, &MainWindow::onIssueTrackerActionTriggered);
|
||||
|
@ -678,6 +695,15 @@ void MainWindow::connectSignals()
|
|||
connect(m_ui.actionAbout, &QAction::triggered, this, &MainWindow::onAboutActionTriggered);
|
||||
connect(m_ui.actionCheckForUpdates, &QAction::triggered, this, &MainWindow::onCheckForUpdatesActionTriggered);
|
||||
connect(m_ui.actionMemory_Card_Editor, &QAction::triggered, this, &MainWindow::onToolsMemoryCardEditorTriggered);
|
||||
connect(m_ui.actionGridViewShowTitles, &QAction::triggered, m_game_list_widget, &GameListWidget::setShowCoverTitles);
|
||||
connect(m_ui.actionGridViewZoomIn, &QAction::triggered, m_game_list_widget, [this]() {
|
||||
if (isShowingGameList())
|
||||
m_game_list_widget->listZoomIn();
|
||||
});
|
||||
connect(m_ui.actionGridViewZoomOut, &QAction::triggered, m_game_list_widget, [this]() {
|
||||
if (isShowingGameList())
|
||||
m_game_list_widget->listZoomOut();
|
||||
});
|
||||
|
||||
connect(m_host_interface, &QtHostInterface::errorReported, this, &MainWindow::reportError,
|
||||
Qt::BlockingQueuedConnection);
|
||||
|
|
|
@ -67,6 +67,7 @@ private Q_SLOTS:
|
|||
void onViewToolbarActionToggled(bool checked);
|
||||
void onViewStatusBarActionToggled(bool checked);
|
||||
void onViewGameListActionTriggered();
|
||||
void onViewGameGridActionTriggered();
|
||||
void onViewSystemDisplayTriggered();
|
||||
void onGitHubRepositoryActionTriggered();
|
||||
void onIssueTrackerActionTriggered();
|
||||
|
@ -91,6 +92,7 @@ private:
|
|||
void connectSignals();
|
||||
void addThemeToMenu(const QString& name, const QString& key);
|
||||
void updateEmulationActions(bool starting, bool running);
|
||||
bool isShowingGameList() const;
|
||||
void switchToGameListView();
|
||||
void switchToEmulationView();
|
||||
void saveStateToConfig();
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<string>DuckStation</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset>
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<normaloff>:/icons/duck.png</normaloff>:/icons/duck.png</iconset>
|
||||
</property>
|
||||
<widget class="QStackedWidget" name="mainContainer">
|
||||
|
@ -42,7 +42,7 @@
|
|||
<string>Change Disc</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<normaloff>:/icons/media-optical.png</normaloff>:/icons/media-optical.png</iconset>
|
||||
</property>
|
||||
<widget class="QMenu" name="menuChangeDiscFromPlaylist">
|
||||
|
@ -60,7 +60,7 @@
|
|||
<string>Cheats</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<normaloff>:/icons/conical-flask-red.png</normaloff>:/icons/conical-flask-red.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
|
@ -69,7 +69,7 @@
|
|||
<string>Load State</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<normaloff>:/icons/document-open.png</normaloff>:/icons/document-open.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
|
@ -78,7 +78,7 @@
|
|||
<string>Save State</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<normaloff>:/icons/document-save.png</normaloff>:/icons/document-save.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
|
@ -115,7 +115,7 @@
|
|||
<addaction name="actionFullscreen"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionGeneralSettings"/>
|
||||
<addaction name="actionBIOSSettings"/>
|
||||
<addaction name="actionBIOSSettings"/>
|
||||
<addaction name="actionConsoleSettings"/>
|
||||
<addaction name="actionGameListSettings"/>
|
||||
<addaction name="actionHotkeySettings"/>
|
||||
|
@ -183,7 +183,12 @@
|
|||
<addaction name="actionViewStatusBar"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionViewGameList"/>
|
||||
<addaction name="actionViewGameGrid"/>
|
||||
<addaction name="actionViewSystemDisplay"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionGridViewShowTitles"/>
|
||||
<addaction name="actionGridViewZoomIn"/>
|
||||
<addaction name="actionGridViewZoomOut"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menu_Tools">
|
||||
<property name="title">
|
||||
|
@ -236,7 +241,7 @@
|
|||
<widget class="QStatusBar" name="statusBar"/>
|
||||
<action name="actionStartDisc">
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<normaloff>:/icons/drive-optical.png</normaloff>:/icons/drive-optical.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
|
@ -245,7 +250,7 @@
|
|||
</action>
|
||||
<action name="actionStartBios">
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<normaloff>:/icons/drive-removable-media.png</normaloff>:/icons/drive-removable-media.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
|
@ -254,7 +259,7 @@
|
|||
</action>
|
||||
<action name="actionScanForNewGames">
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<normaloff>:/icons/folder-open.png</normaloff>:/icons/folder-open.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
|
@ -263,7 +268,7 @@
|
|||
</action>
|
||||
<action name="actionRescanAllGames">
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<normaloff>:/icons/view-refresh.png</normaloff>:/icons/view-refresh.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
|
@ -272,7 +277,7 @@
|
|||
</action>
|
||||
<action name="actionPowerOff">
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<normaloff>:/icons/system-shutdown.png</normaloff>:/icons/system-shutdown.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
|
@ -281,7 +286,7 @@
|
|||
</action>
|
||||
<action name="actionReset">
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<normaloff>:/icons/view-refresh.png</normaloff>:/icons/view-refresh.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
|
@ -293,7 +298,7 @@
|
|||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<normaloff>:/icons/media-playback-pause.png</normaloff>:/icons/media-playback-pause.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
|
@ -302,7 +307,7 @@
|
|||
</action>
|
||||
<action name="actionLoadState">
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<normaloff>:/icons/document-open.png</normaloff>:/icons/document-open.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
|
@ -311,7 +316,7 @@
|
|||
</action>
|
||||
<action name="actionSaveState">
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<normaloff>:/icons/document-save.png</normaloff>:/icons/document-save.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
|
@ -334,7 +339,7 @@
|
|||
</action>
|
||||
<action name="actionConsoleSettings">
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<normaloff>:/icons/utilities-system-monitor.png</normaloff>:/icons/utilities-system-monitor.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
|
@ -343,7 +348,7 @@
|
|||
</action>
|
||||
<action name="actionControllerSettings">
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<normaloff>:/icons/input-gaming.png</normaloff>:/icons/input-gaming.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
|
@ -352,7 +357,7 @@
|
|||
</action>
|
||||
<action name="actionHotkeySettings">
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<normaloff>:/icons/applications-other.png</normaloff>:/icons/applications-other.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
|
@ -361,7 +366,7 @@
|
|||
</action>
|
||||
<action name="actionDisplaySettings">
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<normaloff>:/icons/video-display.png</normaloff>:/icons/video-display.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
|
@ -370,7 +375,7 @@
|
|||
</action>
|
||||
<action name="actionEnhancementSettings">
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<normaloff>:/icons/antialias-icon.png</normaloff>:/icons/antialias-icon.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
|
@ -379,7 +384,7 @@
|
|||
</action>
|
||||
<action name="actionPostProcessingSettings">
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<normaloff>:/icons/applications-graphics.png</normaloff>:/icons/applications-graphics.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
|
@ -388,7 +393,7 @@
|
|||
</action>
|
||||
<action name="actionFullscreen">
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<normaloff>:/icons/view-fullscreen.png</normaloff>:/icons/view-fullscreen.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
|
@ -427,7 +432,7 @@
|
|||
</action>
|
||||
<action name="actionChangeDisc">
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<normaloff>:/icons/media-optical.png</normaloff>:/icons/media-optical.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
|
@ -436,7 +441,7 @@
|
|||
</action>
|
||||
<action name="actionCheats">
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<normaloff>:/icons/conical-flask-red.png</normaloff>:/icons/conical-flask-red.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
|
@ -445,7 +450,7 @@
|
|||
</action>
|
||||
<action name="actionAudioSettings">
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<normaloff>:/icons/audio-card.png</normaloff>:/icons/audio-card.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
|
@ -454,7 +459,7 @@
|
|||
</action>
|
||||
<action name="actionGameListSettings">
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<normaloff>:/icons/folder-open.png</normaloff>:/icons/folder-open.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
|
@ -463,7 +468,7 @@
|
|||
</action>
|
||||
<action name="actionGeneralSettings">
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<normaloff>:/icons/applications-system.png</normaloff>:/icons/applications-system.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
|
@ -472,7 +477,7 @@
|
|||
</action>
|
||||
<action name="actionAdvancedSettings">
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<normaloff>:/icons/applications-development.png</normaloff>:/icons/applications-development.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
|
@ -481,7 +486,7 @@
|
|||
</action>
|
||||
<action name="actionAddGameDirectory">
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<normaloff>:/icons/edit-find.png</normaloff>:/icons/edit-find.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
|
@ -490,7 +495,7 @@
|
|||
</action>
|
||||
<action name="actionSettings">
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<normaloff>:/icons/preferences-system.png</normaloff>:/icons/preferences-system.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
|
@ -601,7 +606,7 @@
|
|||
</action>
|
||||
<action name="actionScreenshot">
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<normaloff>:/icons/camera-photo.png</normaloff>:/icons/camera-photo.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
|
@ -610,7 +615,7 @@
|
|||
</action>
|
||||
<action name="actionMemoryCardSettings">
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<normaloff>:/icons/media-flash-24.png</normaloff>:/icons/media-flash-24.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
|
@ -619,7 +624,7 @@
|
|||
</action>
|
||||
<action name="actionResumeLastState">
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<normaloff>:/icons/media-playback-start.png</normaloff>:/icons/media-playback-start.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
|
@ -669,9 +674,41 @@
|
|||
<string>Memory &Card Editor</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionViewGameGrid">
|
||||
<property name="text">
|
||||
<string>Game Grid</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionGridViewShowTitles">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Show Titles (Grid View)</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionGridViewZoomIn">
|
||||
<property name="text">
|
||||
<string>Zoom In (Grid View)</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl++</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionGridViewZoomOut">
|
||||
<property name="text">
|
||||
<string>Zoom Out (Grid View)</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+-</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="resources/icons.qrc"/>
|
||||
<include location="resources/resources.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
|
BIN
src/duckstation-qt/resources/icons/cover-placeholder.png
Normal file
BIN
src/duckstation-qt/resources/icons/cover-placeholder.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 209 KiB |
|
@ -23,6 +23,7 @@
|
|||
<file>icons/camera-video@2x.png</file>
|
||||
<file>icons/conical-flask-red.png</file>
|
||||
<file>icons/conical-flask-red@2x.png</file>
|
||||
<file>icons/cover-placeholder.png</file>
|
||||
<file>icons/document-open.png</file>
|
||||
<file>icons/document-open@2x.png</file>
|
||||
<file>icons/document-save.png</file>
|
||||
|
|
Loading…
Reference in a new issue