diff --git a/data/resources/images/flags/German.svg b/data/resources/images/flags/German.svg
new file mode 100644
index 000000000..d466e44a6
--- /dev/null
+++ b/data/resources/images/flags/German.svg
@@ -0,0 +1,8 @@
+
+
+
\ No newline at end of file
diff --git a/data/resources/images/flags/Japanese.svg b/data/resources/images/flags/Japanese.svg
new file mode 100644
index 000000000..5b83bd738
--- /dev/null
+++ b/data/resources/images/flags/Japanese.svg
@@ -0,0 +1,6 @@
+
+
\ No newline at end of file
diff --git a/data/resources/images/flags/NTSC-J.svg b/data/resources/images/flags/NTSC-J.svg
new file mode 100644
index 000000000..5b83bd738
--- /dev/null
+++ b/data/resources/images/flags/NTSC-J.svg
@@ -0,0 +1,6 @@
+
+
\ No newline at end of file
diff --git a/data/resources/images/flags/NTSC-U.svg b/data/resources/images/flags/NTSC-U.svg
new file mode 100644
index 000000000..0bf506b35
--- /dev/null
+++ b/data/resources/images/flags/NTSC-U.svg
@@ -0,0 +1,118 @@
+
+
\ No newline at end of file
diff --git a/data/resources/images/flags/Non-PS1.svg b/data/resources/images/flags/Non-PS1.svg
new file mode 100644
index 000000000..5fad98b0e
--- /dev/null
+++ b/data/resources/images/flags/Non-PS1.svg
@@ -0,0 +1,7 @@
+
+
+
\ No newline at end of file
diff --git a/data/resources/images/flags/Other.svg b/data/resources/images/flags/Other.svg
new file mode 100644
index 000000000..aff713e43
--- /dev/null
+++ b/data/resources/images/flags/Other.svg
@@ -0,0 +1,8 @@
+
+
+
\ No newline at end of file
diff --git a/data/resources/images/flags/PAL.svg b/data/resources/images/flags/PAL.svg
new file mode 100644
index 000000000..a605f684e
--- /dev/null
+++ b/data/resources/images/flags/PAL.svg
@@ -0,0 +1,31 @@
+
+
+
\ No newline at end of file
diff --git a/data/resources/images/flags/Spanish.svg b/data/resources/images/flags/Spanish.svg
new file mode 100644
index 000000000..7460f559a
--- /dev/null
+++ b/data/resources/images/flags/Spanish.svg
@@ -0,0 +1,705 @@
+
+
\ No newline at end of file
diff --git a/data/resources/images/flags/sources.txt b/data/resources/images/flags/sources.txt
new file mode 100644
index 000000000..e3f073817
--- /dev/null
+++ b/data/resources/images/flags/sources.txt
@@ -0,0 +1,2 @@
+Spanish.svg: https://commons.wikimedia.org/wiki/File:Flag_of_Spain.svg
+Other.svg: https://en.wikipedia.org/wiki/File:Flag_with_question_mark.svg
\ No newline at end of file
diff --git a/src/common/small_string.cpp b/src/common/small_string.cpp
index 4a213b759..d1d8b4537 100644
--- a/src/common/small_string.cpp
+++ b/src/common/small_string.cpp
@@ -864,6 +864,14 @@ void SmallStringBase::resize(u32 new_size, char fill, bool shrink_if_smaller)
}
}
+void SmallStringBase::set_size(u32 new_size, bool shrink_if_smaller /*= false*/)
+{
+ DebugAssert(new_size <= m_buffer_size);
+ m_length = new_size;
+ if (shrink_if_smaller)
+ shrink_to_fit();
+}
+
void SmallStringBase::update_size()
{
m_length = static_cast(std::strlen(m_buffer));
diff --git a/src/common/small_string.h b/src/common/small_string.h
index 4a892b38f..67fea7a18 100644
--- a/src/common/small_string.h
+++ b/src/common/small_string.h
@@ -158,6 +158,9 @@ public:
// Cuts characters off the string to reduce it to len bytes long.
void resize(u32 new_size, char fill = ' ', bool shrink_if_smaller = false);
+ // sets the size externally, use with data()
+ void set_size(u32 new_size, bool shrink_if_smaller = false);
+
// updates the internal length counter when the string is externally modified
void update_size();
diff --git a/src/core/fullscreen_ui.cpp b/src/core/fullscreen_ui.cpp
index cc6e028ed..05de29302 100644
--- a/src/core/fullscreen_ui.cpp
+++ b/src/core/fullscreen_ui.cpp
@@ -6434,13 +6434,20 @@ void FullscreenUI::DrawGameList(const ImVec2& heading_size)
// region
{
- const TinyString flag_texture =
- TinyString::from_format("fullscreenui/{}.png", Settings::GetDiscRegionName(selected_entry->region));
- ImGui::TextUnformatted(FSUI_CSTR("Region: "));
+ const bool display_as_language = (selected_entry->dbentry && selected_entry->dbentry->HasAnyLanguage());
+ ImGui::TextUnformatted(display_as_language ? FSUI_CSTR("Language: ") : FSUI_CSTR("Region: "));
ImGui::SameLine();
- ImGui::Image(GetCachedTextureAsync(flag_texture.c_str()), LayoutScale(23.0f, 16.0f));
+ ImGui::Image(GetCachedTexture(selected_entry->GetLanguageIconFileName(), 23, 16), LayoutScale(23.0f, 16.0f));
ImGui::SameLine();
- ImGui::Text(" (%s)", Settings::GetDiscRegionDisplayName(selected_entry->region));
+ if (display_as_language)
+ {
+ ImGui::TextWrapped(" (%s, %s)", selected_entry->dbentry->GetLanguagesString().c_str(),
+ Settings::GetDiscRegionName(selected_entry->region));
+ }
+ else
+ {
+ ImGui::TextWrapped(" (%s)", Settings::GetDiscRegionName(selected_entry->region));
+ }
}
// genre
@@ -6448,9 +6455,7 @@ void FullscreenUI::DrawGameList(const ImVec2& heading_size)
ImGui::Text(FSUI_CSTR("Genre: %s"), selected_entry->dbentry->genre.c_str());
// release date
- char release_date_str[64];
- selected_entry->GetReleaseDateString(release_date_str, sizeof(release_date_str));
- ImGui::Text(FSUI_CSTR("Release Date: %s"), release_date_str);
+ ImGui::Text(FSUI_CSTR("Release Date: %s"), selected_entry->GetReleaseDateString().c_str());
// compatibility
ImGui::TextUnformatted(FSUI_CSTR("Compatibility: "));
diff --git a/src/core/game_list.cpp b/src/core/game_list.cpp
index 090e802b9..f045764d9 100644
--- a/src/core/game_list.cpp
+++ b/src/core/game_list.cpp
@@ -28,8 +28,8 @@
#include "fmt/format.h"
#include
-#include
#include
+#include
#include
#include
#include
@@ -990,21 +990,34 @@ std::string_view GameList::Entry::GetLanguageIcon() const
return ret;
}
-size_t GameList::Entry::GetReleaseDateString(char* buffer, size_t buffer_size) const
+TinyString GameList::Entry::GetLanguageIconFileName() const
{
- if (!dbentry || dbentry->release_date == 0)
- return StringUtil::Strlcpy(buffer, "Unknown", buffer_size);
+ return TinyString::from_format("images/flags/{}.svg", GetLanguageIcon());
+}
- std::time_t date_as_time = static_cast(dbentry->release_date);
+TinyString GameList::Entry::GetReleaseDateString() const
+{
+ TinyString ret;
+
+ if (!dbentry || dbentry->release_date == 0)
+ {
+ ret.append(TRANSLATE_SV("GameList", "Unknown"));
+ }
+ else
+ {
+ std::time_t date_as_time = static_cast(dbentry->release_date);
#ifdef _WIN32
- tm date_tm = {};
- gmtime_s(&date_tm, &date_as_time);
+ tm date_tm = {};
+ gmtime_s(&date_tm, &date_as_time);
#else
- tm date_tm = {};
- gmtime_r(&date_as_time, &date_tm);
+ tm date_tm = {};
+ gmtime_r(&date_as_time, &date_tm);
#endif
- return std::strftime(buffer, buffer_size, "%d %B %Y", &date_tm);
+ ret.set_size(static_cast(std::strftime(ret.data(), ret.buffer_size(), "%d %B %Y", &date_tm)));
+ }
+
+ return ret;
}
std::string GameList::GetPlayedTimeFile()
diff --git a/src/core/game_list.h b/src/core/game_list.h
index 8a587b8aa..e8cb106fa 100644
--- a/src/core/game_list.h
+++ b/src/core/game_list.h
@@ -58,7 +58,9 @@ struct Entry
std::string_view GetLanguageIcon() const;
- size_t GetReleaseDateString(char* buffer, size_t buffer_size) const;
+ TinyString GetLanguageIconFileName() const;
+
+ TinyString GetReleaseDateString() const;
ALWAYS_INLINE bool IsDisc() const { return (type == EntryType::Disc); }
ALWAYS_INLINE bool IsDiscSet() const { return (type == EntryType::DiscSet); }
diff --git a/src/duckstation-qt/gamelistmodel.cpp b/src/duckstation-qt/gamelistmodel.cpp
index 67e3b31fa..f2a62f9c4 100644
--- a/src/duckstation-qt/gamelistmodel.cpp
+++ b/src/duckstation-qt/gamelistmodel.cpp
@@ -284,6 +284,21 @@ const QPixmap& GameListModel::getIconPixmapForEntry(const GameList::Entry* ge) c
return m_type_pixmaps[static_cast(ge->type)];
}
+const QPixmap& GameListModel::getFlagPixmapForEntry(const GameList::Entry* ge) const
+{
+ static constexpr u32 FLAG_PIXMAP_WIDTH = 42;
+ static constexpr u32 FLAG_PIXMAP_HEIGHT = 30;
+
+ const std::string_view name = ge->GetLanguageIcon();
+ auto it = m_flag_pixmap_cache.find(name);
+ if (it != m_flag_pixmap_cache.end())
+ return it->second;
+
+ const QIcon icon(QString::fromStdString(QtHost::GetResourcePath(ge->GetLanguageIconFileName(), true)));
+ it = m_flag_pixmap_cache.emplace(name, icon.pixmap(FLAG_PIXMAP_WIDTH, FLAG_PIXMAP_HEIGHT)).first;
+ return it->second;
+}
+
QIcon GameListModel::getIconForGame(const QString& path)
{
QIcon ret;
@@ -544,7 +559,7 @@ QVariant GameListModel::data(const QModelIndex& index, int role, const GameList:
case Column_Region:
{
- return m_region_pixmaps[static_cast(ge->region)];
+ return getFlagPixmapForEntry(ge);
}
case Column_Compatibility:
@@ -788,9 +803,6 @@ void GameListModel::loadThemeSpecificImages()
{
for (u32 i = 0; i < static_cast(GameList::EntryType::Count); i++)
m_type_pixmaps[i] = QtUtils::GetIconForEntryType(static_cast(i)).pixmap(QSize(24, 24));
-
- for (u32 i = 0; i < static_cast(DiscRegion::Count); i++)
- m_region_pixmaps[i] = QtUtils::GetIconForRegion(static_cast(i)).pixmap(42, 30);
}
void GameListModel::loadCommonImages()
diff --git a/src/duckstation-qt/gamelistmodel.h b/src/duckstation-qt/gamelistmodel.h
index 4ebabfd9c..c011e02fe 100644
--- a/src/duckstation-qt/gamelistmodel.h
+++ b/src/duckstation-qt/gamelistmodel.h
@@ -111,6 +111,7 @@ private:
void invalidateCoverForPath(const std::string& path);
const QPixmap& getIconPixmapForEntry(const GameList::Entry* ge) const;
+ const QPixmap& getFlagPixmapForEntry(const GameList::Entry* ge) const;
static void fixIconPixmapSize(QPixmap& pm);
static QString formatTimespan(time_t timespan);
@@ -123,12 +124,13 @@ private:
std::array m_column_display_names;
std::array(GameList::EntryType::Count)> m_type_pixmaps;
- std::array(DiscRegion::Count)> m_region_pixmaps;
std::array(GameDatabase::CompatibilityRating::Count)> m_compatibility_pixmaps;
QPixmap m_placeholder_pixmap;
QPixmap m_loading_pixmap;
+ mutable PreferUnorderedStringMap m_flag_pixmap_cache;
+
mutable LRUCache m_cover_pixmap_cache;
mutable LRUCache m_memcard_pixmap_cache;
diff --git a/src/duckstation-qt/qthost.cpp b/src/duckstation-qt/qthost.cpp
index 35c6c7c0d..5660de4e4 100644
--- a/src/duckstation-qt/qthost.cpp
+++ b/src/duckstation-qt/qthost.cpp
@@ -103,7 +103,6 @@ static void SetDefaultSettings(SettingsInterface& si, bool system, bool controll
static void MigrateSettings();
static void SaveSettings();
static bool RunSetupWizard();
-static std::string GetResourcePath(std::string_view name, bool allow_override);
static std::optional DownloadFile(QWidget* parent, const QString& title, std::string url, std::vector* data);
static void InitializeEarlyConsole();
static void HookSignals();
@@ -1948,7 +1947,7 @@ void Host::OnInputDeviceDisconnected(InputBindingKey key, std::string_view ident
}
}
-ALWAYS_INLINE std::string QtHost::GetResourcePath(std::string_view filename, bool allow_override)
+std::string QtHost::GetResourcePath(std::string_view filename, bool allow_override)
{
return allow_override ? EmuFolders::GetOverridableResourcePath(filename) :
Path::Combine(EmuFolders::Resources, filename);
diff --git a/src/duckstation-qt/qthost.h b/src/duckstation-qt/qthost.h
index f2881b168..5ca702c50 100644
--- a/src/duckstation-qt/qthost.h
+++ b/src/duckstation-qt/qthost.h
@@ -294,6 +294,9 @@ const QIcon& GetAppIcon();
/// Returns the base path for resources. This may be : prefixed, if we're using embedded resources.
QString GetResourcesBasePath();
+/// Returns the path to the specified resource.
+std::string GetResourcePath(std::string_view name, bool allow_override);
+
/// Returns the base settings interface. Should lock before manipulating.
INISettingsInterface* GetBaseSettingsInterface();
diff --git a/src/duckstation-qt/qtutils.cpp b/src/duckstation-qt/qtutils.cpp
index 284cd0d20..f024ee26f 100644
--- a/src/duckstation-qt/qtutils.cpp
+++ b/src/duckstation-qt/qtutils.cpp
@@ -251,11 +251,14 @@ QIcon QtUtils::GetIconForRegion(ConsoleRegion region)
switch (region)
{
case ConsoleRegion::NTSC_J:
- return QIcon(QStringLiteral(":/icons/flag-jp.svg"));
- case ConsoleRegion::PAL:
- return QIcon(QStringLiteral(":/icons/flag-eu.svg"));
+ return QIcon(QString::fromStdString(QtHost::GetResourcePath("images/flags/NTSC-J.svg", true)));
+
case ConsoleRegion::NTSC_U:
- return QIcon(QStringLiteral(":/icons/flag-uc.svg"));
+ return QIcon(QString::fromStdString(QtHost::GetResourcePath("images/flags/NTSC-U.svg", true)));
+
+ case ConsoleRegion::PAL:
+ return QIcon(QString::fromStdString(QtHost::GetResourcePath("images/flags/PAL.svg", true)));
+
default:
return QIcon::fromTheme(QStringLiteral("file-unknow-line"));
}
@@ -266,11 +269,14 @@ QIcon QtUtils::GetIconForRegion(DiscRegion region)
switch (region)
{
case DiscRegion::NTSC_J:
- return QIcon(QStringLiteral(":/icons/flag-jp.svg"));
- case DiscRegion::PAL:
- return QIcon(QStringLiteral(":/icons/flag-eu.svg"));
+ return QIcon(QString::fromStdString(QtHost::GetResourcePath("images/flags/NTSC-J.svg", true)));
+
case DiscRegion::NTSC_U:
- return QIcon(QStringLiteral(":/icons/flag-uc.svg"));
+ return QIcon(QString::fromStdString(QtHost::GetResourcePath("images/flags/NTSC-U.svg", true)));
+
+ case DiscRegion::PAL:
+ return QIcon(QString::fromStdString(QtHost::GetResourcePath("images/flags/PAL.svg", true)));
+
case DiscRegion::Other:
case DiscRegion::NonPS1:
default: