mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2025-02-01 04:05:38 +00:00
Qt: Show memory card icons in game list
You can disable it if you really hate it.
This commit is contained in:
parent
8659c8cca6
commit
9143116616
|
@ -1,13 +1,16 @@
|
|||
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
||||
|
||||
#include "gamelistmodel.h"
|
||||
#include "qthost.h"
|
||||
#include "qtutils.h"
|
||||
|
||||
#include "core/system.h"
|
||||
|
||||
#include "common/file_system.h"
|
||||
#include "common/path.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/system.h"
|
||||
#include "qthost.h"
|
||||
#include "qtutils.h"
|
||||
|
||||
#include <QtConcurrent/QtConcurrent>
|
||||
#include <QtCore/QDate>
|
||||
#include <QtCore/QDateTime>
|
||||
|
@ -26,6 +29,11 @@ static constexpr int COVER_ART_HEIGHT = 512;
|
|||
static constexpr int COVER_ART_SPACING = 32;
|
||||
static constexpr int MIN_COVER_CACHE_SIZE = 256;
|
||||
|
||||
static std::string getMemoryCardIconCachePath()
|
||||
{
|
||||
return Path::Combine(EmuFolders::Cache, "memcard_icons.cache");
|
||||
}
|
||||
|
||||
static int DPRScale(int size, float dpr)
|
||||
{
|
||||
return static_cast<int>(static_cast<float>(size) * dpr);
|
||||
|
@ -114,15 +122,32 @@ const char* GameListModel::getColumnName(Column col)
|
|||
return s_column_names[static_cast<int>(col)];
|
||||
}
|
||||
|
||||
GameListModel::GameListModel(float cover_scale, bool show_cover_titles, QObject* parent /* = nullptr */)
|
||||
: QAbstractTableModel(parent), m_show_titles_for_covers(show_cover_titles)
|
||||
GameListModel::GameListModel(float cover_scale, bool show_cover_titles, bool show_game_icons,
|
||||
QObject* parent /* = nullptr */)
|
||||
: QAbstractTableModel(parent), m_show_titles_for_covers(show_cover_titles), m_show_game_icons(show_game_icons),
|
||||
m_memcard_icon_cache(getMemoryCardIconCachePath()), m_memcard_pixmap_cache(128)
|
||||
{
|
||||
loadCommonImages();
|
||||
setCoverScale(cover_scale);
|
||||
setColumnDisplayNames();
|
||||
|
||||
if (m_show_game_icons)
|
||||
m_memcard_icon_cache.Reload();
|
||||
}
|
||||
|
||||
GameListModel::~GameListModel() = default;
|
||||
|
||||
void GameListModel::setShowGameIcons(bool enabled)
|
||||
{
|
||||
m_show_game_icons = enabled;
|
||||
|
||||
beginResetModel();
|
||||
m_memcard_pixmap_cache.Clear();
|
||||
if (enabled)
|
||||
m_memcard_icon_cache.Reload();
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void GameListModel::setCoverScale(float scale)
|
||||
{
|
||||
if (m_cover_scale == scale)
|
||||
|
@ -224,6 +249,31 @@ QString GameListModel::formatTimespan(time_t timespan)
|
|||
return qApp->translate("GameList", "%n minutes", "", minutes);
|
||||
}
|
||||
|
||||
const QPixmap& GameListModel::getIconForEntry(const GameList::Entry* ge) const
|
||||
{
|
||||
// We only do this for discs/disc sets for now.
|
||||
if (m_show_game_icons && (!ge->serial.empty() && (ge->IsDisc() || ge->IsDiscSet())))
|
||||
{
|
||||
QPixmap* item = m_memcard_pixmap_cache.Lookup(ge->serial);
|
||||
if (item)
|
||||
return *item;
|
||||
|
||||
const MemoryCardImage::IconFrame* icon = m_memcard_icon_cache.Lookup(ge->serial, ge->path);
|
||||
if (icon)
|
||||
{
|
||||
const QImage image(reinterpret_cast<const uchar*>(icon->pixels), MemoryCardImage::ICON_WIDTH,
|
||||
MemoryCardImage::ICON_HEIGHT, QImage::Format_RGBA8888);
|
||||
return *m_memcard_pixmap_cache.Insert(ge->serial, QPixmap::fromImage(image));
|
||||
}
|
||||
else
|
||||
{
|
||||
return *m_memcard_pixmap_cache.Insert(ge->serial, m_type_pixmaps[static_cast<u32>(ge->type)]);
|
||||
}
|
||||
}
|
||||
|
||||
return m_type_pixmaps[static_cast<u32>(ge->type)];
|
||||
}
|
||||
|
||||
int GameListModel::getCoverArtWidth() const
|
||||
{
|
||||
return std::max(static_cast<int>(static_cast<float>(COVER_ART_WIDTH) * m_cover_scale), 1);
|
||||
|
@ -407,8 +457,7 @@ QVariant GameListModel::data(const QModelIndex& index, int role) const
|
|||
{
|
||||
case Column_Type:
|
||||
{
|
||||
// TODO: Test for settings
|
||||
return m_type_pixmaps[static_cast<u32>(ge->type)];
|
||||
return getIconForEntry(ge);
|
||||
}
|
||||
|
||||
case Column_Region:
|
||||
|
@ -455,6 +504,10 @@ QVariant GameListModel::headerData(int section, Qt::Orientation orientation, int
|
|||
void GameListModel::refresh()
|
||||
{
|
||||
beginResetModel();
|
||||
|
||||
// Invalidate memcard LRU cache, forcing a re-query of the memcard timestamps.
|
||||
m_memcard_pixmap_cache.Clear();
|
||||
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
|
||||
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/game_database.h"
|
||||
#include "core/game_list.h"
|
||||
#include "core/memory_card_icon_cache.h"
|
||||
#include "core/types.h"
|
||||
|
||||
#include "common/heterogeneous_containers.h"
|
||||
|
@ -46,7 +47,7 @@ public:
|
|||
static std::optional<Column> getColumnIdForName(std::string_view name);
|
||||
static const char* getColumnName(Column col);
|
||||
|
||||
GameListModel(float cover_scale, bool show_cover_titles, QObject* parent = nullptr);
|
||||
GameListModel(float cover_scale, bool show_cover_titles, bool show_game_icons, QObject* parent = nullptr);
|
||||
~GameListModel();
|
||||
|
||||
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||
|
@ -66,6 +67,9 @@ public:
|
|||
bool getShowCoverTitles() const { return m_show_titles_for_covers; }
|
||||
void setShowCoverTitles(bool enabled) { m_show_titles_for_covers = enabled; }
|
||||
|
||||
bool getShowGameIcons() const { return m_show_game_icons; }
|
||||
void setShowGameIcons(bool enabled);
|
||||
|
||||
float getCoverScale() const { return m_cover_scale; }
|
||||
void setCoverScale(float scale);
|
||||
int getCoverArtWidth() const;
|
||||
|
@ -84,10 +88,13 @@ private:
|
|||
void loadOrGenerateCover(const GameList::Entry* ge);
|
||||
void invalidateCoverForPath(const std::string& path);
|
||||
|
||||
const QPixmap& getIconForEntry(const GameList::Entry* ge) const;
|
||||
|
||||
static QString formatTimespan(time_t timespan);
|
||||
|
||||
float m_cover_scale = 0.0f;
|
||||
bool m_show_titles_for_covers = false;
|
||||
bool m_show_game_icons = false;
|
||||
|
||||
std::array<QString, Column_Count> m_column_display_names;
|
||||
std::array<QPixmap, static_cast<int>(GameList::EntryType::Count)> m_type_pixmaps;
|
||||
|
@ -98,4 +105,7 @@ private:
|
|||
QPixmap m_loading_pixmap;
|
||||
|
||||
mutable LRUCache<std::string, QPixmap> m_cover_pixmap_cache;
|
||||
};
|
||||
|
||||
mutable MemoryCardIconCache m_memcard_icon_cache;
|
||||
mutable LRUCache<std::string, QPixmap> m_memcard_pixmap_cache;
|
||||
};
|
||||
|
|
|
@ -114,7 +114,8 @@ void GameListWidget::initialize()
|
|||
const float cover_scale = Host::GetBaseFloatSettingValue("UI", "GameListCoverArtScale", 0.45f);
|
||||
const bool show_cover_titles = Host::GetBaseBoolSettingValue("UI", "GameListShowCoverTitles", true);
|
||||
const bool merge_disc_sets = Host::GetBaseBoolSettingValue("UI", "GameListMergeDiscSets", true);
|
||||
m_model = new GameListModel(cover_scale, show_cover_titles, this);
|
||||
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);
|
||||
|
@ -242,6 +243,11 @@ bool GameListWidget::isMergingDiscSets() const
|
|||
return m_sort_model->isMergingDiscSets();
|
||||
}
|
||||
|
||||
bool GameListWidget::isShowingGameIcons() const
|
||||
{
|
||||
return m_model->getShowGameIcons();
|
||||
}
|
||||
|
||||
void GameListWidget::refresh(bool invalidate_cache)
|
||||
{
|
||||
cancelRefresh();
|
||||
|
@ -476,6 +482,16 @@ void GameListWidget::setMergeDiscSets(bool enabled)
|
|||
emit layoutChange();
|
||||
}
|
||||
|
||||
void GameListWidget::setShowGameIcons(bool enabled)
|
||||
{
|
||||
if (m_model->getShowGameIcons() == enabled)
|
||||
return;
|
||||
|
||||
Host::SetBaseBoolSettingValue("UI", "GameListShowGameIcons", enabled);
|
||||
Host::CommitBaseSettingChanges();
|
||||
m_model->setShowGameIcons(enabled);
|
||||
}
|
||||
|
||||
void GameListWidget::updateToolbar()
|
||||
{
|
||||
const bool grid_view = isShowingGameGrid();
|
||||
|
|
|
@ -53,6 +53,7 @@ public:
|
|||
bool isShowingGameGrid() const;
|
||||
bool isShowingGridCoverTitles() const;
|
||||
bool isMergingDiscSets() const;
|
||||
bool isShowingGameIcons() const;
|
||||
|
||||
const GameList::Entry* getSelectedEntry() const;
|
||||
|
||||
|
@ -85,6 +86,7 @@ public Q_SLOTS:
|
|||
void showGameGrid();
|
||||
void setShowCoverTitles(bool enabled);
|
||||
void setMergeDiscSets(bool enabled);
|
||||
void setShowGameIcons(bool enabled);
|
||||
void gridZoomIn();
|
||||
void gridZoomOut();
|
||||
void gridIntScale(int int_scale);
|
||||
|
|
|
@ -1637,6 +1637,7 @@ void MainWindow::setupAdditionalUi()
|
|||
m_game_list_widget->initialize();
|
||||
m_ui.actionGridViewShowTitles->setChecked(m_game_list_widget->isShowingGridCoverTitles());
|
||||
m_ui.actionMergeDiscSets->setChecked(m_game_list_widget->isMergingDiscSets());
|
||||
m_ui.actionShowGameIcons->setChecked(m_game_list_widget->isShowingGameIcons());
|
||||
if (s_use_central_widget)
|
||||
{
|
||||
m_ui.mainContainer = nullptr; // setCentralWidget() will delete this
|
||||
|
@ -2096,6 +2097,7 @@ void MainWindow::connectSignals()
|
|||
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionEnableGDBServer, "Debug", "EnableGDBServer", false);
|
||||
connect(m_ui.actionOpenDataDirectory, &QAction::triggered, this, &MainWindow::onToolsOpenDataDirectoryTriggered);
|
||||
connect(m_ui.actionMergeDiscSets, &QAction::triggered, m_game_list_widget, &GameListWidget::setMergeDiscSets);
|
||||
connect(m_ui.actionShowGameIcons, &QAction::triggered, m_game_list_widget, &GameListWidget::setShowGameIcons);
|
||||
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())
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
<string>DuckStation</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<iconset resource="resources/duckstation-qt.qrc">
|
||||
<normaloff>:/icons/duck.png</normaloff>:/icons/duck.png</iconset>
|
||||
</property>
|
||||
<property name="unifiedTitleAndToolBarOnMac">
|
||||
|
@ -220,7 +220,8 @@
|
|||
<addaction name="actionFullscreen"/>
|
||||
<addaction name="menuWindowSize"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionMergeDiscSets" />
|
||||
<addaction name="actionMergeDiscSets"/>
|
||||
<addaction name="actionShowGameIcons"/>
|
||||
<addaction name="actionGridViewShowTitles"/>
|
||||
<addaction name="actionGridViewZoomIn"/>
|
||||
<addaction name="actionGridViewZoomOut"/>
|
||||
|
@ -444,7 +445,7 @@
|
|||
</action>
|
||||
<action name="actionGitHubRepository">
|
||||
<property name="icon">
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<iconset resource="resources/duckstation-qt.qrc">
|
||||
<normaloff>:/icons/github.png</normaloff>:/icons/github.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
|
@ -453,7 +454,7 @@
|
|||
</action>
|
||||
<action name="actionIssueTracker">
|
||||
<property name="icon">
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<iconset resource="resources/duckstation-qt.qrc">
|
||||
<normaloff>:/icons/IssueTracker.png</normaloff>:/icons/IssueTracker.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
|
@ -462,7 +463,7 @@
|
|||
</action>
|
||||
<action name="actionDiscordServer">
|
||||
<property name="icon">
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<iconset resource="resources/duckstation-qt.qrc">
|
||||
<normaloff>:/icons/discord.png</normaloff>:/icons/discord.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
|
@ -484,7 +485,7 @@
|
|||
</action>
|
||||
<action name="actionAboutQt">
|
||||
<property name="icon">
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<iconset resource="resources/duckstation-qt.qrc">
|
||||
<normaloff>:/icons/QT.png</normaloff>:/icons/QT.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
|
@ -493,7 +494,7 @@
|
|||
</action>
|
||||
<action name="actionAbout">
|
||||
<property name="icon">
|
||||
<iconset resource="resources/resources.qrc">
|
||||
<iconset resource="resources/duckstation-qt.qrc">
|
||||
<normaloff>:/icons/duck_64.png</normaloff>:/icons/duck_64.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
|
@ -951,9 +952,20 @@
|
|||
<string>Memory &Scanner</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionShowGameIcons">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Show Game Icons (List View)</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="resources/resources.qrc"/>
|
||||
<include location="resources/duckstation-qt.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
|
|
@ -248,9 +248,10 @@ bool QtHost::SaveGameSettings(SettingsInterface* sif, bool delete_if_empty)
|
|||
return true;
|
||||
}
|
||||
|
||||
QIcon QtHost::GetAppIcon()
|
||||
const QIcon& QtHost::GetAppIcon()
|
||||
{
|
||||
return QIcon(QStringLiteral(":/icons/duck.png"));
|
||||
static QIcon icon = QIcon(QStringLiteral(":/icons/duck.png"));
|
||||
return icon;
|
||||
}
|
||||
|
||||
std::optional<bool> QtHost::DownloadFile(QWidget* parent, const QString& title, std::string url, std::vector<u8>* data)
|
||||
|
|
|
@ -270,7 +270,7 @@ QString GetAppNameAndVersion();
|
|||
QString GetAppConfigSuffix();
|
||||
|
||||
/// Returns the main application icon.
|
||||
QIcon GetAppIcon();
|
||||
const QIcon& GetAppIcon();
|
||||
|
||||
/// Returns the base path for resources. This may be : prefixed, if we're using embedded resources.
|
||||
QString GetResourcesBasePath();
|
||||
|
|
Loading…
Reference in a new issue