Qt: Add game list language override option

This commit is contained in:
Stenzek 2024-11-24 23:24:39 +10:00
parent 70a4b5c9f2
commit 852239ec8a
No known key found for this signature in database
11 changed files with 313 additions and 199 deletions

View File

@ -6882,7 +6882,7 @@ void FullscreenUI::DrawGameList(const ImVec2& heading_size)
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(GetCachedTexture(selected_entry->GetLanguageIconFileName(), 23, 16), LayoutScale(23.0f, 16.0f));
ImGui::Image(GetCachedTexture(selected_entry->GetLanguageIconName(), 23, 16), LayoutScale(23.0f, 16.0f));
ImGui::SameLine();
if (display_as_language)
{

View File

@ -21,6 +21,7 @@
#include "ryml.hpp"
#include <bit>
#include <iomanip>
#include <memory>
#include <optional>
@ -312,6 +313,24 @@ std::optional<GameDatabase::Language> GameDatabase::ParseLanguageName(std::strin
return std::nullopt;
}
TinyString GameDatabase::GetLanguageFlagResourceName(std::string_view language_name)
{
return TinyString::from_format("images/flags/{}.svg", language_name);
}
std::string_view GameDatabase::Entry::GetLanguageFlagName(DiscRegion region) const
{
// If there's only one language, this is the flag we want to use.
// Except if it's English, then we want to use the disc region's flag.
std::string_view ret;
if (languages.count() == 1 && !languages.test(static_cast<size_t>(GameDatabase::Language::English)))
ret = GameDatabase::GetLanguageName(static_cast<GameDatabase::Language>(std::countr_zero(languages.to_ulong())));
else
ret = Settings::GetDiscRegionName(region);
return ret;
}
SmallString GameDatabase::Entry::GetLanguagesString() const
{
SmallString ret;

View File

@ -130,8 +130,9 @@ struct Entry
ALWAYS_INLINE bool HasTrait(Trait trait) const { return traits[static_cast<int>(trait)]; }
ALWAYS_INLINE bool HasLanguage(Language language) const { return languages.test(static_cast<size_t>(language)); }
ALWAYS_INLINE bool HasAnyLanguage() const { return !languages.none(); }
ALWAYS_INLINE bool HasAnyLanguage() const { return languages.any(); }
std::string_view GetLanguageFlagName(DiscRegion region) const;
SmallString GetLanguagesString() const;
void ApplySettings(Settings& settings, bool display_osd_messages) const;
@ -156,6 +157,7 @@ const char* GetCompatibilityRatingDisplayName(CompatibilityRating rating);
const char* GetLanguageName(Language language);
std::optional<Language> ParseLanguageName(std::string_view str);
TinyString GetLanguageFlagResourceName(std::string_view language_name);
/// Map of track hashes for image verification
struct TrackData

View File

@ -117,6 +117,8 @@ static PlayedTimeEntry UpdatePlayedTimeFile(const std::string& path, const std::
std::time_t add_time);
static std::string GetCustomPropertiesFile();
static bool PutCustomPropertiesField(INISettingsInterface& ini, const std::string& path, const char* field,
const char* value);
static FileSystem::ManagedCFilePtr OpenMemoryCardTimestampCache(bool for_write);
static bool UpdateMemcardTimestampCache(const MemcardTimestampCacheEntry& entry);
@ -627,6 +629,21 @@ void GameList::ApplyCustomAttributes(const std::string& path, Entry* entry,
WARNING_LOG("Invalid region '{}' in custom attributes for '{}'", custom_region_str.value(), path);
}
}
const std::optional<TinyString> custom_language_str =
custom_attributes_ini.GetOptionalTinyStringValue(path.c_str(), "Language");
if (custom_language_str.has_value())
{
const std::optional<GameDatabase::Language> custom_region =
GameDatabase::ParseLanguageName(custom_region_str.value());
if (custom_region.has_value())
{
entry->custom_language = custom_region.value();
}
else
{
WARNING_LOG("Invalid language '{}' in custom attributes for '{}'", custom_region_str.value(), path);
}
}
}
std::unique_lock<std::recursive_mutex> GameList::GetLock()
@ -990,26 +1007,20 @@ std::string GameList::GetNewCoverImagePathForEntry(const Entry* entry, const cha
std::string_view GameList::Entry::GetLanguageIcon() const
{
// If there's only one language, this is the flag we want to use.
// Except if it's English, then we want to use the disc region's flag.
std::string_view ret;
if (dbentry && dbentry->languages.count() == 1 &&
!dbentry->languages.test(static_cast<size_t>(GameDatabase::Language::English)))
{
ret = GameDatabase::GetLanguageName(
static_cast<GameDatabase::Language>(std::countr_zero(dbentry->languages.to_ulong())));
}
if (custom_language != GameDatabase::Language::MaxCount)
ret = GameDatabase::GetLanguageName(custom_language);
else if (dbentry)
ret = dbentry->GetLanguageFlagName(region);
else
{
ret = Settings::GetDiscRegionName(region);
}
return ret;
}
TinyString GameList::Entry::GetLanguageIconFileName() const
TinyString GameList::Entry::GetLanguageIconName() const
{
return TinyString::from_format("images/flags/{}.svg", GetLanguageIcon());
return GameDatabase::GetLanguageFlagResourceName(GetLanguageIcon());
}
TinyString GameList::Entry::GetCompatibilityIconFileName() const
@ -1518,28 +1529,37 @@ std::string GameList::GetCustomPropertiesFile()
return Path::Combine(EmuFolders::DataRoot, "custom_properties.ini");
}
void GameList::SaveCustomTitleForPath(const std::string& path, const std::string& custom_title)
bool GameList::PutCustomPropertiesField(INISettingsInterface& ini, const std::string& path, const char* field,
const char* value)
{
INISettingsInterface custom_attributes_ini(GetCustomPropertiesFile());
custom_attributes_ini.Load();
ini.Load();
if (!custom_title.empty())
if (value && *value != '\0')
{
custom_attributes_ini.SetStringValue(path.c_str(), "Title", custom_title.c_str());
ini.SetStringValue(path.c_str(), field, value);
}
else
{
custom_attributes_ini.DeleteValue(path.c_str(), "Title");
custom_attributes_ini.RemoveEmptySections();
ini.DeleteValue(path.c_str(), field);
ini.RemoveEmptySections();
}
Error error;
if (!custom_attributes_ini.Save(&error))
if (!ini.Save(&error))
{
ERROR_LOG("Failed to save custom attributes: {}", error.GetDescription());
return;
return false;
}
return true;
}
bool GameList::SaveCustomTitleForPath(const std::string& path, const std::string& custom_title)
{
INISettingsInterface custom_attributes_ini(GetCustomPropertiesFile());
if (!PutCustomPropertiesField(custom_attributes_ini, path, "Title", custom_title.c_str()))
return false;
if (!custom_title.empty())
{
// Can skip the rescan and just update the value directly.
@ -1556,28 +1576,18 @@ void GameList::SaveCustomTitleForPath(const std::string& path, const std::string
// Let the cache update by rescanning. Only need to do this on deletion, to get the original value.
RescanCustomAttributesForPath(path, custom_attributes_ini);
}
return true;
}
void GameList::SaveCustomRegionForPath(const std::string& path, const std::optional<DiscRegion> custom_region)
bool GameList::SaveCustomRegionForPath(const std::string& path, const std::optional<DiscRegion> custom_region)
{
INISettingsInterface custom_attributes_ini(GetCustomPropertiesFile());
custom_attributes_ini.Load();
if (custom_region.has_value())
if (!PutCustomPropertiesField(custom_attributes_ini, path, "Region",
custom_region.has_value() ? Settings::GetDiscRegionName(custom_region.value()) :
nullptr))
{
custom_attributes_ini.SetStringValue(path.c_str(), "Region", Settings::GetDiscRegionName(custom_region.value()));
}
else
{
custom_attributes_ini.DeleteValue(path.c_str(), "Region");
custom_attributes_ini.RemoveEmptySections();
}
Error error;
if (!custom_attributes_ini.Save(&error))
{
ERROR_LOG("Failed to save custom attributes: {}", error.GetDescription());
return;
return false;
}
if (custom_region.has_value())
@ -1596,6 +1606,28 @@ void GameList::SaveCustomRegionForPath(const std::string& path, const std::optio
// Let the cache update by rescanning. Only need to do this on deletion, to get the original value.
RescanCustomAttributesForPath(path, custom_attributes_ini);
}
return true;
}
bool GameList::SaveCustomLanguageForPath(const std::string& path,
const std::optional<GameDatabase::Language> custom_language)
{
INISettingsInterface custom_attributes_ini(GetCustomPropertiesFile());
if (!PutCustomPropertiesField(custom_attributes_ini, path, "Language",
custom_language.has_value() ? GameDatabase::GetLanguageName(custom_language.value()) :
nullptr))
{
return false;
}
// Don't need to rescan, since there's no original value to restore.
auto lock = GetLock();
Entry* entry = GetMutableEntryForPath(path);
if (entry)
entry->custom_language = custom_language.value_or(GameDatabase::Language::MaxCount);
return true;
}
std::string GameList::GetCustomTitleForPath(const std::string_view path)

View File

@ -40,6 +40,7 @@ struct Entry
bool disc_set_member = false;
bool has_custom_title = false;
bool has_custom_region = false;
GameDatabase::Language custom_language = GameDatabase::Language::MaxCount;
std::string path;
std::string serial;
@ -57,13 +58,14 @@ struct Entry
std::string_view GetLanguageIcon() const;
TinyString GetLanguageIconFileName() const;
TinyString GetLanguageIconName() const;
TinyString GetCompatibilityIconFileName() const;
TinyString GetReleaseDateString() const;
ALWAYS_INLINE bool IsDisc() const { return (type == EntryType::Disc); }
ALWAYS_INLINE bool IsDiscSet() const { return (type == EntryType::DiscSet); }
ALWAYS_INLINE bool HasCustomLanguage() const { return (custom_language != GameDatabase::Language::MaxCount); }
ALWAYS_INLINE EntryType GetSortType() const { return (type == EntryType::DiscSet) ? EntryType::Disc : type; }
};
@ -128,8 +130,9 @@ bool DownloadCovers(const std::vector<std::string>& url_templates, bool use_seri
std::function<void(const Entry*, std::string)> save_callback = {});
// Custom properties support
void SaveCustomTitleForPath(const std::string& path, const std::string& custom_title);
void SaveCustomRegionForPath(const std::string& path, const std::optional<DiscRegion> custom_region);
bool SaveCustomTitleForPath(const std::string& path, const std::string& custom_title);
bool SaveCustomRegionForPath(const std::string& path, const std::optional<DiscRegion> custom_region);
bool SaveCustomLanguageForPath(const std::string& path, const std::optional<GameDatabase::Language> custom_language);
std::string GetCustomTitleForPath(const std::string_view path);
std::optional<DiscRegion> GetCustomRegionForPath(const std::string_view path);

View File

@ -294,7 +294,7 @@ const QPixmap& GameListModel::getFlagPixmapForEntry(const GameList::Entry* ge) c
if (it != m_flag_pixmap_cache.end())
return it->second;
const QIcon icon(QString::fromStdString(QtHost::GetResourcePath(ge->GetLanguageIconFileName(), true)));
const QIcon icon(QString::fromStdString(QtHost::GetResourcePath(ge->GetLanguageIconName(), true)));
it = m_flag_pixmap_cache.emplace(name, icon.pixmap(FLAG_PIXMAP_WIDTH, FLAG_PIXMAP_HEIGHT)).first;
return it->second;
}

View File

@ -52,6 +52,15 @@ GameSummaryWidget::GameSummaryWidget(const std::string& path, const std::string&
static_cast<GameDatabase::CompatibilityRating>(i))));
}
// I hate this so much.
m_ui.customLanguage->addItem(QtUtils::GetIconForLanguage(entry->GetLanguageFlagName(region)),
tr("Show Default Flag"));
for (u32 i = 0; i < static_cast<u32>(GameDatabase::Language::MaxCount); i++)
{
const char* language_name = GameDatabase::GetLanguageName(static_cast<GameDatabase::Language>(i));
m_ui.customLanguage->addItem(QtUtils::GetIconForLanguage(language_name), QString::fromUtf8(language_name));
}
populateUi(path, serial, region, entry);
connect(m_ui.compatibilityComments, &QToolButton::clicked, this, &GameSummaryWidget::onCompatibilityCommentsClicked);
@ -69,6 +78,7 @@ GameSummaryWidget::GameSummaryWidget(const std::string& path, const std::string&
connect(m_ui.restoreTitle, &QAbstractButton::clicked, this, [this]() { setCustomTitle(std::string()); });
connect(m_ui.region, &QComboBox::currentIndexChanged, this, [this](int index) { setCustomRegion(index); });
connect(m_ui.restoreRegion, &QAbstractButton::clicked, this, [this]() { setCustomRegion(-1); });
connect(m_ui.customLanguage, &QComboBox::currentIndexChanged, this, &GameSummaryWidget::onCustomLanguageChanged);
}
GameSummaryWidget::~GameSummaryWidget() = default;
@ -147,6 +157,8 @@ void GameSummaryWidget::populateUi(const std::string& path, const std::string& s
else
m_ui.releaseInfo->setText(tr("Unknown"));
m_ui.languages->setText(QtUtils::StringViewToQString(entry->GetLanguagesString()));
QString controllers;
if (entry->supported_controllers != 0 && entry->supported_controllers != static_cast<u16>(-1))
{
@ -201,7 +213,10 @@ void GameSummaryWidget::populateCustomAttributes()
auto lock = GameList::GetLock();
const GameList::Entry* entry = GameList::GetEntryForPath(m_path);
if (!entry || entry->IsDiscSet())
{
m_ui.customLanguage->setEnabled(false);
return;
}
{
QSignalBlocker sb(m_ui.title);
@ -214,6 +229,12 @@ void GameSummaryWidget::populateCustomAttributes()
m_ui.region->setCurrentIndex(static_cast<int>(entry->region));
m_ui.restoreRegion->setEnabled(entry->has_custom_region);
}
{
QSignalBlocker sb(m_ui.customLanguage);
m_ui.customLanguage->setCurrentIndex(entry->HasCustomLanguage() ? (static_cast<u32>(entry->custom_language) + 1) :
0);
}
}
void GameSummaryWidget::updateWindowTitle()
@ -238,7 +259,15 @@ void GameSummaryWidget::setCustomRegion(int region)
GameList::SaveCustomRegionForPath(m_path, (region >= 0) ? std::optional<DiscRegion>(static_cast<DiscRegion>(region)) :
std::optional<DiscRegion>());
populateCustomAttributes();
updateWindowTitle();
g_main_window->refreshGameListModel();
}
void GameSummaryWidget::onCustomLanguageChanged(int language)
{
GameList::SaveCustomLanguageForPath(
m_path, (language > 0) ? std::optional<GameDatabase::Language>(static_cast<GameDatabase::Language>(language - 1)) :
std::optional<GameDatabase::Language>());
populateCustomAttributes();
g_main_window->refreshGameListModel();
}

View File

@ -27,6 +27,7 @@ public:
void reloadGameSettings();
private Q_SLOTS:
void onCustomLanguageChanged(int language);
void onCompatibilityCommentsClicked();
void onInputProfileChanged(int index);
void onEditInputProfileClicked();

View File

@ -30,67 +30,36 @@
<property name="bottomMargin">
<number>0</number>
</property>
<item row="8" column="1">
<widget class="QLineEdit" name="genre">
<item row="10" column="1">
<widget class="QLineEdit" name="developer">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_7">
<item>
<widget class="QLineEdit" name="title">
<property name="placeholderText">
<string>Clear the line to restore the original title...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="restoreTitle">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Restore</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="6" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QComboBox" name="region">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="restoreRegion">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Restore</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Image Path:</string>
<item row="13" column="1">
<widget class="QLineEdit" name="controllers">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="path">
<item row="17" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Tracks:</string>
</property>
</widget>
</item>
<item row="14" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Input Profile:</string>
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="QLineEdit" name="genre">
<property name="readOnly">
<bool>true</bool>
</property>
@ -103,7 +72,35 @@
</property>
</widget>
</item>
<item row="18" column="0" colspan="2">
<item row="12" column="1">
<widget class="QLineEdit" name="releaseInfo">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="serial">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="13" column="0">
<widget class="QLabel" name="label_15">
<property name="text">
<string>Controllers:</string>
</property>
</widget>
</item>
<item row="12" column="0">
<widget class="QLabel" name="label_14">
<property name="text">
<string>Release Info:</string>
</property>
</widget>
</item>
<item row="19" column="0" colspan="2">
<widget class="QTableWidget" name="tracks">
<property name="editTriggers">
<set>QAbstractItemView::EditTrigger::NoEditTriggers</set>
@ -146,77 +143,7 @@
</column>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Region:</string>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="label_12">
<property name="text">
<string>Developer:</string>
</property>
</widget>
</item>
<item row="12" column="0">
<widget class="QLabel" name="label_15">
<property name="text">
<string>Controllers:</string>
</property>
</widget>
</item>
<item row="16" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Tracks:</string>
</property>
</widget>
</item>
<item row="12" column="1">
<widget class="QLineEdit" name="controllers">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QComboBox" name="entryType">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="11" column="0">
<widget class="QLabel" name="label_14">
<property name="text">
<string>Release Info:</string>
</property>
</widget>
</item>
<item row="13" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Input Profile:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="serial">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>Genre:</string>
</property>
</widget>
</item>
<item row="16" column="1">
<item row="17" column="1">
<layout class="QHBoxLayout" name="verifyLayout" stretch="0,1,0">
<item>
<spacer name="verifySpacer">
@ -253,42 +180,42 @@
</item>
</layout>
</item>
<item row="9" column="1">
<widget class="QLineEdit" name="developer">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
<item row="3" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_7">
<item>
<widget class="QLineEdit" name="title">
<property name="placeholderText">
<string>Clear the line to restore the original title...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="restoreTitle">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Restore</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_10">
<item row="0" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Type:</string>
<string>Image Path:</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Title:</string>
<item row="5" column="1">
<widget class="QComboBox" name="entryType">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="11" column="1">
<widget class="QLineEdit" name="releaseInfo">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Compatibility:</string>
</property>
</widget>
</item>
<item row="7" column="1">
<item row="8" column="1">
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,0">
<item>
<widget class="QComboBox" name="compatibility">
@ -309,7 +236,28 @@
</item>
</layout>
</item>
<item row="13" column="1">
<item row="9" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>Genre:</string>
</property>
</widget>
</item>
<item row="10" column="0">
<widget class="QLabel" name="label_12">
<property name="text">
<string>Developer:</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Region:</string>
</property>
</widget>
</item>
<item row="14" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_2" stretch="1,0">
<item>
<widget class="QComboBox" name="inputProfile"/>
@ -323,6 +271,79 @@
</item>
</layout>
</item>
<item row="8" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Compatibility:</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>Type:</string>
</property>
</widget>
</item>
<item row="6" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QComboBox" name="region">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="restoreRegion">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Restore</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="path">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Title:</string>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Languages:</string>
</property>
</widget>
</item>
<item row="7" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_3" stretch="1,0">
<item>
<widget class="QLineEdit" name="languages">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="customLanguage"/>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>

View File

@ -312,6 +312,12 @@ QIcon QtUtils::GetIconForCompatibility(GameDatabase::CompatibilityRating rating)
QtHost::GetResourcePath(TinyString::from_format("images/star-{}.svg", static_cast<u32>(rating)), true)));
}
QIcon QtUtils::GetIconForLanguage(std::string_view language_name)
{
return QIcon(
QString::fromStdString(QtHost::GetResourcePath(GameDatabase::GetLanguageFlagResourceName(language_name), true)));
}
qreal QtUtils::GetDevicePixelRatioForWidget(const QWidget* widget)
{
const QScreen* screen_for_ratio = widget->screen();

View File

@ -113,6 +113,7 @@ QIcon GetIconForRegion(DiscRegion region);
/// Returns icon for entry type.
QIcon GetIconForEntryType(GameList::EntryType type);
QIcon GetIconForCompatibility(GameDatabase::CompatibilityRating rating);
QIcon GetIconForLanguage(std::string_view language_name);
/// Returns the pixel ratio/scaling factor for a widget.
qreal GetDevicePixelRatioForWidget(const QWidget* widget);