summaryrefslogtreecommitdiff
path: root/src/citron/game_list_p.h
diff options
context:
space:
mode:
authorZephyron <zephyron@citron-emu.org>2024-12-31 16:19:25 +1000
committerZephyron <zephyron@citron-emu.org>2024-12-31 16:19:25 +1000
commit9427e27e24a7135880ee2881c3c44988e174b41a (patch)
tree83f0062a35be144f6b162eaa823c5b3c7620146e /src/citron/game_list_p.h
parentb35ae725d20960411e8588b11c12a2d55f86c9d0 (diff)
chore: update project branding to citron
Diffstat (limited to 'src/citron/game_list_p.h')
-rw-r--r--src/citron/game_list_p.h408
1 files changed, 408 insertions, 0 deletions
diff --git a/src/citron/game_list_p.h b/src/citron/game_list_p.h
new file mode 100644
index 000000000..c330b574f
--- /dev/null
+++ b/src/citron/game_list_p.h
@@ -0,0 +1,408 @@
+// SPDX-FileCopyrightText: 2015 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <map>
+#include <string>
+#include <utility>
+
+#include <QCoreApplication>
+#include <QFileInfo>
+#include <QObject>
+#include <QStandardItem>
+#include <QString>
+#include <QWidget>
+
+#include "common/common_types.h"
+#include "common/logging/log.h"
+#include "common/string_util.h"
+#include "yuzu/play_time_manager.h"
+#include "yuzu/uisettings.h"
+#include "yuzu/util/util.h"
+
+enum class GameListItemType {
+ Game = QStandardItem::UserType + 1,
+ CustomDir = QStandardItem::UserType + 2,
+ SdmcDir = QStandardItem::UserType + 3,
+ UserNandDir = QStandardItem::UserType + 4,
+ SysNandDir = QStandardItem::UserType + 5,
+ AddDir = QStandardItem::UserType + 6,
+ Favorites = QStandardItem::UserType + 7,
+};
+
+Q_DECLARE_METATYPE(GameListItemType);
+
+/**
+ * Gets the default icon (for games without valid title metadata)
+ * @param size The desired width and height of the default icon.
+ * @return QPixmap default icon
+ */
+static QPixmap GetDefaultIcon(u32 size) {
+ QPixmap icon(size, size);
+ icon.fill(Qt::transparent);
+ return icon;
+}
+
+class GameListItem : public QStandardItem {
+
+public:
+ // used to access type from item index
+ static constexpr int TypeRole = Qt::UserRole + 1;
+ static constexpr int SortRole = Qt::UserRole + 2;
+ GameListItem() = default;
+ explicit GameListItem(const QString& string) : QStandardItem(string) {
+ setData(string, SortRole);
+ }
+};
+
+/**
+ * A specialization of GameListItem for path values.
+ * This class ensures that for every full path value it holds, a correct string representation
+ * of just the filename (with no extension) will be displayed to the user.
+ * If this class receives valid title metadata, it will also display game icons and titles.
+ */
+class GameListItemPath : public GameListItem {
+public:
+ static constexpr int TitleRole = SortRole + 1;
+ static constexpr int FullPathRole = SortRole + 2;
+ static constexpr int ProgramIdRole = SortRole + 3;
+ static constexpr int FileTypeRole = SortRole + 4;
+
+ GameListItemPath() = default;
+ GameListItemPath(const QString& game_path, const std::vector<u8>& picture_data,
+ const QString& game_name, const QString& game_type, u64 program_id) {
+ setData(type(), TypeRole);
+ setData(game_path, FullPathRole);
+ setData(game_name, TitleRole);
+ setData(qulonglong(program_id), ProgramIdRole);
+ setData(game_type, FileTypeRole);
+
+ const u32 size = UISettings::values.game_icon_size.GetValue();
+
+ QPixmap picture;
+ if (!picture.loadFromData(picture_data.data(), static_cast<u32>(picture_data.size()))) {
+ picture = GetDefaultIcon(size);
+ }
+ picture = picture.scaled(size, size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
+
+ setData(picture, Qt::DecorationRole);
+ }
+
+ int type() const override {
+ return static_cast<int>(GameListItemType::Game);
+ }
+
+ QVariant data(int role) const override {
+ if (role == Qt::DisplayRole || role == SortRole) {
+ std::string filename;
+ Common::SplitPath(data(FullPathRole).toString().toStdString(), nullptr, &filename,
+ nullptr);
+
+ const std::array<QString, 4> row_data{{
+ QString::fromStdString(filename),
+ data(FileTypeRole).toString(),
+ QString::fromStdString(fmt::format("0x{:016X}", data(ProgramIdRole).toULongLong())),
+ data(TitleRole).toString(),
+ }};
+
+ const auto& row1 = row_data.at(UISettings::values.row_1_text_id.GetValue());
+ const int row2_id = UISettings::values.row_2_text_id.GetValue();
+
+ if (role == SortRole) {
+ return row1.toLower();
+ }
+
+ // None
+ if (row2_id == 4) {
+ return row1;
+ }
+
+ const auto& row2 = row_data.at(row2_id);
+
+ if (row1 == row2) {
+ return row1;
+ }
+
+ return QStringLiteral("%1\n %2").arg(row1, row2);
+ }
+
+ return GameListItem::data(role);
+ }
+};
+
+class GameListItemCompat : public GameListItem {
+ Q_DECLARE_TR_FUNCTIONS(GameListItemCompat)
+public:
+ static constexpr int CompatNumberRole = SortRole;
+ GameListItemCompat() = default;
+ explicit GameListItemCompat(const QString& compatibility) {
+ setData(type(), TypeRole);
+
+ struct CompatStatus {
+ QString color;
+ const char* text;
+ const char* tooltip;
+ };
+ // clang-format off
+ const auto ingame_status =
+ CompatStatus{QStringLiteral("#f2d624"), QT_TR_NOOP("Ingame"), QT_TR_NOOP("Game starts, but crashes or major glitches prevent it from being completed.")};
+ static const std::map<QString, CompatStatus> status_data = {
+ {QStringLiteral("0"), {QStringLiteral("#5c93ed"), QT_TR_NOOP("Perfect"), QT_TR_NOOP("Game can be played without issues.")}},
+ {QStringLiteral("1"), {QStringLiteral("#47d35c"), QT_TR_NOOP("Playable"), QT_TR_NOOP("Game functions with minor graphical or audio glitches and is playable from start to finish.")}},
+ {QStringLiteral("2"), ingame_status},
+ {QStringLiteral("3"), ingame_status}, // Fallback for the removed "Okay" category
+ {QStringLiteral("4"), {QStringLiteral("#FF0000"), QT_TR_NOOP("Intro/Menu"), QT_TR_NOOP("Game loads, but is unable to progress past the Start Screen.")}},
+ {QStringLiteral("5"), {QStringLiteral("#828282"), QT_TR_NOOP("Won't Boot"), QT_TR_NOOP("The game crashes when attempting to startup.")}},
+ {QStringLiteral("99"), {QStringLiteral("#000000"), QT_TR_NOOP("Not Tested"), QT_TR_NOOP("The game has not yet been tested.")}},
+ };
+ // clang-format on
+
+ auto iterator = status_data.find(compatibility);
+ if (iterator == status_data.end()) {
+ LOG_WARNING(Frontend, "Invalid compatibility number {}", compatibility.toStdString());
+ return;
+ }
+ const CompatStatus& status = iterator->second;
+ setData(compatibility, CompatNumberRole);
+ setText(tr(status.text));
+ setToolTip(tr(status.tooltip));
+ setData(CreateCirclePixmapFromColor(status.color), Qt::DecorationRole);
+ }
+
+ int type() const override {
+ return static_cast<int>(GameListItemType::Game);
+ }
+
+ bool operator<(const QStandardItem& other) const override {
+ return data(CompatNumberRole).value<QString>() <
+ other.data(CompatNumberRole).value<QString>();
+ }
+};
+
+/**
+ * A specialization of GameListItem for size values.
+ * This class ensures that for every numerical size value it holds (in bytes), a correct
+ * human-readable string representation will be displayed to the user.
+ */
+class GameListItemSize : public GameListItem {
+public:
+ static constexpr int SizeRole = SortRole;
+
+ GameListItemSize() = default;
+ explicit GameListItemSize(const qulonglong size_bytes) {
+ setData(type(), TypeRole);
+ setData(size_bytes, SizeRole);
+ }
+
+ void setData(const QVariant& value, int role) override {
+ // By specializing setData for SizeRole, we can ensure that the numerical and string
+ // representations of the data are always accurate and in the correct format.
+ if (role == SizeRole) {
+ qulonglong size_bytes = value.toULongLong();
+ GameListItem::setData(ReadableByteSize(size_bytes), Qt::DisplayRole);
+ GameListItem::setData(value, SizeRole);
+ } else {
+ GameListItem::setData(value, role);
+ }
+ }
+
+ int type() const override {
+ return static_cast<int>(GameListItemType::Game);
+ }
+
+ /**
+ * This operator is, in practice, only used by the TreeView sorting systems.
+ * Override it so that it will correctly sort by numerical value instead of by string
+ * representation.
+ */
+ bool operator<(const QStandardItem& other) const override {
+ return data(SizeRole).toULongLong() < other.data(SizeRole).toULongLong();
+ }
+};
+
+/**
+ * GameListItem for Play Time values.
+ * This object stores the play time of a game in seconds, and its readable
+ * representation in minutes/hours
+ */
+class GameListItemPlayTime : public GameListItem {
+public:
+ static constexpr int PlayTimeRole = SortRole;
+
+ GameListItemPlayTime() = default;
+ explicit GameListItemPlayTime(const qulonglong time_seconds) {
+ setData(time_seconds, PlayTimeRole);
+ }
+
+ void setData(const QVariant& value, int role) override {
+ qulonglong time_seconds = value.toULongLong();
+ GameListItem::setData(PlayTime::ReadablePlayTime(time_seconds), Qt::DisplayRole);
+ GameListItem::setData(value, PlayTimeRole);
+ }
+
+ bool operator<(const QStandardItem& other) const override {
+ return data(PlayTimeRole).toULongLong() < other.data(PlayTimeRole).toULongLong();
+ }
+};
+
+class GameListDir : public GameListItem {
+public:
+ static constexpr int GameDirRole = Qt::UserRole + 2;
+
+ explicit GameListDir(UISettings::GameDir& directory,
+ GameListItemType dir_type_ = GameListItemType::CustomDir)
+ : dir_type{dir_type_} {
+ setData(type(), TypeRole);
+
+ UISettings::GameDir* game_dir = &directory;
+ setData(QVariant(UISettings::values.game_dirs.indexOf(directory)), GameDirRole);
+
+ const int icon_size = UISettings::values.folder_icon_size.GetValue();
+ switch (dir_type) {
+ case GameListItemType::SdmcDir:
+ setData(
+ QIcon::fromTheme(QStringLiteral("sd_card"))
+ .pixmap(icon_size)
+ .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
+ Qt::DecorationRole);
+ setData(QObject::tr("Installed SD Titles"), Qt::DisplayRole);
+ break;
+ case GameListItemType::UserNandDir:
+ setData(
+ QIcon::fromTheme(QStringLiteral("chip"))
+ .pixmap(icon_size)
+ .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
+ Qt::DecorationRole);
+ setData(QObject::tr("Installed NAND Titles"), Qt::DisplayRole);
+ break;
+ case GameListItemType::SysNandDir:
+ setData(
+ QIcon::fromTheme(QStringLiteral("chip"))
+ .pixmap(icon_size)
+ .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
+ Qt::DecorationRole);
+ setData(QObject::tr("System Titles"), Qt::DisplayRole);
+ break;
+ case GameListItemType::CustomDir: {
+ const QString path = QString::fromStdString(game_dir->path);
+ const QString icon_name =
+ QFileInfo::exists(path) ? QStringLiteral("folder") : QStringLiteral("bad_folder");
+ setData(QIcon::fromTheme(icon_name).pixmap(icon_size).scaled(
+ icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
+ Qt::DecorationRole);
+ setData(path, Qt::DisplayRole);
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ int type() const override {
+ return static_cast<int>(dir_type);
+ }
+
+ /**
+ * Override to prevent automatic sorting between folders and the addDir button.
+ */
+ bool operator<(const QStandardItem& other) const override {
+ return false;
+ }
+
+private:
+ GameListItemType dir_type;
+};
+
+class GameListAddDir : public GameListItem {
+public:
+ explicit GameListAddDir() {
+ setData(type(), TypeRole);
+
+ const int icon_size = UISettings::values.folder_icon_size.GetValue();
+
+ setData(QIcon::fromTheme(QStringLiteral("list-add"))
+ .pixmap(icon_size)
+ .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
+ Qt::DecorationRole);
+ setData(QObject::tr("Add New Game Directory"), Qt::DisplayRole);
+ }
+
+ int type() const override {
+ return static_cast<int>(GameListItemType::AddDir);
+ }
+
+ bool operator<(const QStandardItem& other) const override {
+ return false;
+ }
+};
+
+class GameListFavorites : public GameListItem {
+public:
+ explicit GameListFavorites() {
+ setData(type(), TypeRole);
+
+ const int icon_size = UISettings::values.folder_icon_size.GetValue();
+
+ setData(QIcon::fromTheme(QStringLiteral("star"))
+ .pixmap(icon_size)
+ .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
+ Qt::DecorationRole);
+ setData(QObject::tr("Favorites"), Qt::DisplayRole);
+ }
+
+ int type() const override {
+ return static_cast<int>(GameListItemType::Favorites);
+ }
+
+ bool operator<(const QStandardItem& other) const override {
+ return false;
+ }
+};
+
+class GameList;
+class QHBoxLayout;
+class QTreeView;
+class QLabel;
+class QLineEdit;
+class QToolButton;
+
+class GameListSearchField : public QWidget {
+ Q_OBJECT
+
+public:
+ explicit GameListSearchField(GameList* parent = nullptr);
+
+ QString filterText() const;
+ void setFilterResult(int visible_, int total_);
+
+ void clear();
+ void setFocus();
+
+private:
+ void changeEvent(QEvent*) override;
+ void RetranslateUI();
+
+ class KeyReleaseEater : public QObject {
+ public:
+ explicit KeyReleaseEater(GameList* gamelist_, QObject* parent = nullptr);
+
+ private:
+ GameList* gamelist = nullptr;
+ QString edit_filter_text_old;
+
+ protected:
+ // EventFilter in order to process systemkeys while editing the searchfield
+ bool eventFilter(QObject* obj, QEvent* event) override;
+ };
+ int visible;
+ int total;
+
+ QHBoxLayout* layout_filter = nullptr;
+ QTreeView* tree_view = nullptr;
+ QLabel* label_filter = nullptr;
+ QLineEdit* edit_filter = nullptr;
+ QLabel* label_filter_result = nullptr;
+ QToolButton* button_filter_close = nullptr;
+};