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 @@ + + + + Flag of Germany + + + + \ 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 @@ + + +Japanese flag + + + \ 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 @@ + + +Japanese flag + + + \ 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 @@ + + + Vertically split United States and Canada flag, made for identification of the NTSU-U/C region. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ 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 @@ + + + +European Union flag + + + + + + + + + + + + + + + + + + + + + + + + + + + \ 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: