DolphinQt: cache icons instead of single pixmaps

Fixes dynamically changing dpi scaling.
Load resources from svg if possible.
Currently svg support is not in Qt build in Externals,
and image files need to be added later.
This commit is contained in:
Shawn Hoffman 2023-04-23 03:43:49 -07:00
parent 8c2e924255
commit 51e528e45f
15 changed files with 94 additions and 218 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 926 B

View File

@ -83,7 +83,7 @@ AboutDialog::AboutDialog(QWidget* parent) : QDialog(parent)
"trademarks of Nintendo. Dolphin is not affiliated with Nintendo in any way.")));
QLabel* logo = new QLabel();
logo->setPixmap(Resources::GetMisc(Resources::MiscID::LogoLarge));
logo->setPixmap(Resources::GetAppIcon().pixmap(200, 200));
logo->setContentsMargins(30, 0, 30, 0);
QVBoxLayout* main_layout = new QVBoxLayout;

View File

@ -285,8 +285,6 @@ add_executable(dolphin-emu
QtUtils/UTF8CodePointCountValidator.h
QtUtils/WindowActivationEventFilter.cpp
QtUtils/WindowActivationEventFilter.h
QtUtils/WinIconHelper.cpp
QtUtils/WinIconHelper.h
QtUtils/WrapInScrollArea.cpp
QtUtils/WrapInScrollArea.h
RenderWidget.cpp

View File

@ -90,12 +90,12 @@ void FilesystemWidget::ConnectWidgets()
void FilesystemWidget::PopulateView()
{
// Cache these two icons, the tree will use them a lot.
m_folder_icon = Resources::GetScaledIcon("isoproperties_folder");
m_file_icon = Resources::GetScaledIcon("isoproperties_file");
m_folder_icon = Resources::GetResourceIcon("isoproperties_folder");
m_file_icon = Resources::GetResourceIcon("isoproperties_file");
auto* disc = new QStandardItem(tr("Disc"));
disc->setEditable(false);
disc->setIcon(Resources::GetScaledIcon("isoproperties_disc"));
disc->setIcon(Resources::GetResourceIcon("isoproperties_disc"));
disc->setData(QVariant::fromValue(EntryType::Disc), ENTRY_TYPE);
m_tree_model->appendRow(disc);
m_tree_view->expand(disc->index());
@ -107,7 +107,7 @@ void FilesystemWidget::PopulateView()
auto* item = new QStandardItem;
item->setEditable(false);
item->setIcon(Resources::GetScaledIcon("isoproperties_disc"));
item->setIcon(Resources::GetResourceIcon("isoproperties_disc"));
item->setData(static_cast<qlonglong>(i), ENTRY_PARTITION);
item->setData(QVariant::fromValue(EntryType::Partition), ENTRY_TYPE);

View File

@ -126,11 +126,11 @@ void BreakpointWidget::CreateWidgets()
void BreakpointWidget::UpdateIcons()
{
m_new->setIcon(Resources::GetScaledThemeIcon("debugger_add_breakpoint"));
m_delete->setIcon(Resources::GetScaledThemeIcon("debugger_delete"));
m_clear->setIcon(Resources::GetScaledThemeIcon("debugger_clear"));
m_load->setIcon(Resources::GetScaledThemeIcon("debugger_load"));
m_save->setIcon(Resources::GetScaledThemeIcon("debugger_save"));
m_new->setIcon(Resources::GetThemeIcon("debugger_add_breakpoint"));
m_delete->setIcon(Resources::GetThemeIcon("debugger_delete"));
m_clear->setIcon(Resources::GetThemeIcon("debugger_clear"));
m_load->setIcon(Resources::GetThemeIcon("debugger_load"));
m_save->setIcon(Resources::GetThemeIcon("debugger_save"));
}
void BreakpointWidget::closeEvent(QCloseEvent*)

View File

@ -373,8 +373,7 @@ void CodeViewWidget::Update(const Core::CPUThreadGuard* guard)
if (debug_interface.IsBreakpoint(addr))
{
auto icon =
Resources::GetScaledThemeIcon("debugger_breakpoint").pixmap(QSize(rowh - 2, rowh - 2));
auto icon = Resources::GetThemeIcon("debugger_breakpoint").pixmap(QSize(rowh - 2, rowh - 2));
if (!m_system.GetPowerPC().GetBreakPoints().IsBreakPointEnable(addr))
{
QPixmap disabled_icon(icon.size());

View File

@ -583,7 +583,7 @@ void MemoryViewWidget::UpdateBreakpointTags()
{
m_table->item(i, 0)->setData(
Qt::DecorationRole,
Resources::GetScaledThemeIcon("debugger_breakpoint")
Resources::GetThemeIcon("debugger_breakpoint")
.pixmap(QSize(m_table->rowHeight(0) - 3, m_table->rowHeight(0) - 3)));
}
else

View File

@ -131,11 +131,11 @@ void WatchWidget::ConnectWidgets()
void WatchWidget::UpdateIcons()
{
// TODO: Create a "debugger_add_watch" icon
m_new->setIcon(Resources::GetScaledThemeIcon("debugger_add_breakpoint"));
m_delete->setIcon(Resources::GetScaledThemeIcon("debugger_delete"));
m_clear->setIcon(Resources::GetScaledThemeIcon("debugger_clear"));
m_load->setIcon(Resources::GetScaledThemeIcon("debugger_load"));
m_save->setIcon(Resources::GetScaledThemeIcon("debugger_save"));
m_new->setIcon(Resources::GetThemeIcon("debugger_add_breakpoint"));
m_delete->setIcon(Resources::GetThemeIcon("debugger_delete"));
m_clear->setIcon(Resources::GetThemeIcon("debugger_clear"));
m_load->setIcon(Resources::GetThemeIcon("debugger_load"));
m_save->setIcon(Resources::GetThemeIcon("debugger_save"));
}
void WatchWidget::UpdateButtonsEnabled()

View File

@ -183,7 +183,6 @@
<ClCompile Include="QtUtils\PartiallyClosableTabWidget.cpp" />
<ClCompile Include="QtUtils\UTF8CodePointCountValidator.cpp" />
<ClCompile Include="QtUtils\WindowActivationEventFilter.cpp" />
<ClCompile Include="QtUtils\WinIconHelper.cpp" />
<ClCompile Include="QtUtils\WrapInScrollArea.cpp" />
<ClCompile Include="RenderWidget.cpp" />
<ClCompile Include="ResourcePackManager.cpp" />
@ -240,7 +239,6 @@
<ClInclude Include="QtUtils\QueueOnObject.h" />
<ClInclude Include="QtUtils\RunOnObject.h" />
<ClInclude Include="QtUtils\SignalBlocking.h" />
<ClInclude Include="QtUtils\WinIconHelper.h" />
<ClInclude Include="QtUtils\WrapInScrollArea.h" />
<ClInclude Include="ResourcePackManager.h" />
<ClInclude Include="Resources.h" />

View File

@ -62,13 +62,13 @@ QVariant GameListModel::data(const QModelIndex& index, int role) const
{
case Column::Platform:
if (role == Qt::DecorationRole)
return Resources::GetPlatform(game.GetPlatform());
return Resources::GetPlatform(game.GetPlatform()).pixmap(32, 32);
if (role == SORT_ROLE)
return static_cast<int>(game.GetPlatform());
break;
case Column::Country:
if (role == Qt::DecorationRole)
return Resources::GetCountry(game.GetCountry());
return Resources::GetCountry(game.GetCountry()).pixmap(32, 22);
if (role == SORT_ROLE)
return static_cast<int>(game.GetCountry());
break;
@ -78,7 +78,7 @@ QVariant GameListModel::data(const QModelIndex& index, int role) const
// GameCube banners are 96x32, but Wii banners are 192x64.
QPixmap banner = ToQPixmap(game.GetBannerImage());
if (banner.isNull())
banner = Resources::GetMisc(Resources::MiscID::BannerMissing);
banner = Resources::GetMisc(Resources::MiscID::BannerMissing).pixmap(GAMECUBE_BANNER_SIZE);
banner.setDevicePixelRatio(
std::max(static_cast<qreal>(banner.width()) / GAMECUBE_BANNER_SIZE.width(),

View File

@ -1,93 +0,0 @@
// Copyright 2018 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "DolphinQt/QtUtils/WinIconHelper.h"
#ifdef _WIN32
#include <Windows.h>
// The following code is adapted from qpixmap_win.cpp (c) The Qt Company Ltd. (https://qt.io)
// Licensed under the GNU GPL v3
static inline BITMAPINFO GetBMI(int width, int height, bool topToBottom)
{
BITMAPINFO bmi = {};
auto& bih = bmi.bmiHeader;
bih.biSize = sizeof(BITMAPINFOHEADER);
bih.biWidth = width;
bih.biHeight = topToBottom ? -height : height;
bih.biPlanes = 1;
bih.biBitCount = 32;
bih.biCompression = BI_RGB;
bih.biSizeImage = width * height * 4;
return bmi;
}
static QPixmap PixmapFromHICON(HICON icon)
{
HDC screenDevice = GetDC(0);
HDC hdc = CreateCompatibleDC(screenDevice);
ReleaseDC(0, screenDevice);
ICONINFO iconinfo;
const bool result = GetIconInfo(icon, &iconinfo); // x and y Hotspot describes the icon center
if (!result)
{
qErrnoWarning("QPixmap::fromWinHICON(), failed to GetIconInfo()");
DeleteDC(hdc);
return QPixmap();
}
const int w = iconinfo.xHotspot * 2;
const int h = iconinfo.yHotspot * 2;
BITMAPINFO bitmapInfo = GetBMI(w, h, false);
DWORD* bits;
HBITMAP winBitmap = CreateDIBSection(hdc, &bitmapInfo, DIB_RGB_COLORS, (VOID**)&bits, nullptr, 0);
HGDIOBJ oldhdc = reinterpret_cast<HBITMAP>(SelectObject(hdc, winBitmap));
DrawIconEx(hdc, 0, 0, icon, iconinfo.xHotspot * 2, iconinfo.yHotspot * 2, 0, 0, DI_NORMAL);
QImage image(w, h, QImage::Format_ARGB32_Premultiplied);
if (image.isNull())
return {};
BITMAPINFO bmi = GetBMI(w, h, true);
QScopedArrayPointer<uchar> data(new uchar[bmi.bmiHeader.biSizeImage]);
if (!GetDIBits(hdc, winBitmap, 0, h, data.data(), &bmi, DIB_RGB_COLORS))
return {};
for (int y = 0; y < image.height(); ++y)
{
void* dest = static_cast<void*>(image.scanLine(y));
const void* src = data.data() + y * image.bytesPerLine();
memcpy(dest, src, image.bytesPerLine());
}
// dispose resources created by iconinfo call
DeleteObject(iconinfo.hbmMask);
DeleteObject(iconinfo.hbmColor);
SelectObject(hdc, oldhdc); // restore state
DeleteObject(winBitmap);
DeleteDC(hdc);
return QPixmap::fromImage(image);
}
QIcon WinIconHelper::GetNativeIcon()
{
QIcon icon;
for (int size : {16, 32, 48, 256})
{
HANDLE h = LoadImageW(GetModuleHandleW(nullptr), L"\"DOLPHIN\"", IMAGE_ICON, size, size,
LR_CREATEDIBSECTION);
if (h && h != INVALID_HANDLE_VALUE)
{
auto* icon_handle = static_cast<HICON>(h);
icon.addPixmap(PixmapFromHICON(icon_handle));
}
}
return icon;
}
#endif

View File

@ -1,15 +0,0 @@
// Copyright 2018 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#ifdef _WIN32
#include <QIcon>
namespace WinIconHelper
{
QIcon GetNativeIcon();
};
#endif

View File

@ -3,53 +3,58 @@
#include "DolphinQt/Resources.h"
#include <QGuiApplication>
#include <QFileInfo>
#include <QIcon>
#include <QImageReader>
#include <QPixmap>
#include <QScreen>
#include "Common/Assert.h"
#include "Common/FileUtil.h"
#include "Core/Config/MainSettings.h"
#include "DolphinQt/Settings.h"
#ifdef _WIN32
#include "DolphinQt/QtUtils/WinIconHelper.h"
#endif
bool Resources::m_svg_supported;
QList<QIcon> Resources::m_platforms;
QList<QIcon> Resources::m_countries;
QList<QIcon> Resources::m_misc;
QList<QPixmap> Resources::m_platforms;
QList<QPixmap> Resources::m_countries;
QList<QPixmap> Resources::m_misc;
QIcon Resources::GetIcon(std::string_view name, const QString& dir)
QIcon Resources::LoadNamedIcon(std::string_view name, const QString& dir)
{
QString name_owned = QString::fromLatin1(name.data(), static_cast<int>(name.size()));
QString base_path = dir + QLatin1Char{'/'} + name_owned;
const QString base_path = dir + QLatin1Char{'/'} + QString::fromLatin1(name);
const QString svg_path = base_path + QStringLiteral(".svg");
const auto dpr = QGuiApplication::primaryScreen()->devicePixelRatio();
// Prefer svg
if (m_svg_supported && QFileInfo(svg_path).exists())
return QIcon(svg_path);
QIcon icon(base_path.append(QStringLiteral(".png")));
QIcon icon;
if (dpr > 2)
{
QPixmap pixmap(base_path.append(QStringLiteral("@4x.png")));
auto load_png = [&](int scale) {
QString suffix = QStringLiteral(".png");
if (scale > 1)
suffix = QString::fromLatin1("@%1x.png").arg(scale);
QPixmap pixmap(base_path + suffix);
if (!pixmap.isNull())
{
pixmap.setDevicePixelRatio(4.0);
pixmap.setDevicePixelRatio(scale);
icon.addPixmap(pixmap);
}
}
};
// Since we are caching the files, we need to try and load all known sizes up-front.
// Otherwise, a dynamic change of devicePixelRatio could result in use of non-ideal image from
// cache while a better one exists on disk.
for (auto scale : {1, 2, 4})
load_png(scale);
ASSERT(icon.availableSizes().size() > 0);
return icon;
}
QPixmap Resources::GetPixmap(std::string_view name, const QString& dir)
{
const auto icon = GetIcon(name, dir);
return icon.pixmap(icon.availableSizes()[0]);
}
static QString GetCurrentThemeDir()
{
return QString::fromStdString(File::GetThemeDir(Config::Get(Config::MAIN_THEME_NAME)));
@ -60,27 +65,24 @@ static QString GetResourcesDir()
return QString::fromStdString(File::GetSysDirectory() + "Resources");
}
QIcon Resources::GetScaledIcon(std::string_view name)
QIcon Resources::GetResourceIcon(std::string_view name)
{
return GetIcon(name, GetResourcesDir());
return LoadNamedIcon(name, GetResourcesDir());
}
QIcon Resources::GetScaledThemeIcon(std::string_view name)
QIcon Resources::GetThemeIcon(std::string_view name)
{
return GetIcon(name, GetCurrentThemeDir());
}
QPixmap Resources::GetScaledPixmap(std::string_view name)
{
return GetPixmap(name, GetResourcesDir());
return LoadNamedIcon(name, GetCurrentThemeDir());
}
void Resources::Init()
{
m_svg_supported = QImageReader::supportedImageFormats().contains("svg");
for (std::string_view platform :
{"Platform_Gamecube", "Platform_Wii", "Platform_Wad", "Platform_File"})
{
m_platforms.append(GetScaledPixmap(platform));
m_platforms.append(GetResourceIcon(platform));
}
for (std::string_view country :
@ -88,39 +90,29 @@ void Resources::Init()
"Flag_Italy", "Flag_Korea", "Flag_Netherlands", "Flag_Russia", "Flag_Spain", "Flag_Taiwan",
"Flag_International", "Flag_Unknown"})
{
m_countries.append(GetScaledPixmap(country));
m_countries.append(GetResourceIcon(country));
}
m_misc.append(GetScaledPixmap("nobanner"));
m_misc.append(GetScaledPixmap("dolphin_logo"));
m_misc.append(GetScaledPixmap("Dolphin"));
m_misc.append(GetResourceIcon("nobanner"));
m_misc.append(GetResourceIcon("dolphin_logo"));
}
QPixmap Resources::GetPlatform(DiscIO::Platform platform)
QIcon Resources::GetPlatform(DiscIO::Platform platform)
{
return m_platforms[static_cast<int>(platform)];
}
QPixmap Resources::GetCountry(DiscIO::Country country)
QIcon Resources::GetCountry(DiscIO::Country country)
{
return m_countries[static_cast<int>(country)];
}
QPixmap Resources::GetMisc(MiscID id)
QIcon Resources::GetMisc(MiscID id)
{
return m_misc[static_cast<int>(id)];
}
QIcon Resources::GetAppIcon()
{
QIcon icon;
#ifdef _WIN32
icon = WinIconHelper::GetNativeIcon();
#else
icon.addPixmap(GetScaledPixmap("dolphin_logo"));
icon.addPixmap(GetScaledPixmap("Dolphin"));
#endif
return icon;
return GetMisc(MiscID::Logo);
}

View File

@ -3,8 +3,8 @@
#pragma once
#include <QIcon>
#include <QList>
#include <QPixmap>
#include <string_view>
namespace DiscIO
@ -20,29 +20,26 @@ public:
enum class MiscID
{
BannerMissing,
LogoLarge,
LogoSmall
Logo,
};
static void Init();
static QPixmap GetPlatform(DiscIO::Platform platform);
static QPixmap GetCountry(DiscIO::Country country);
static QIcon GetPlatform(DiscIO::Platform platform);
static QIcon GetCountry(DiscIO::Country country);
static QPixmap GetMisc(MiscID id);
static QIcon GetMisc(MiscID id);
static QIcon GetScaledIcon(std::string_view name);
static QIcon GetScaledThemeIcon(std::string_view name);
static QIcon GetResourceIcon(std::string_view name);
static QIcon GetThemeIcon(std::string_view name);
static QIcon GetAppIcon();
static QPixmap GetScaledPixmap(std::string_view name);
private:
Resources() {}
static QIcon GetIcon(std::string_view name, const QString& dir);
static QPixmap GetPixmap(std::string_view name, const QString& dir);
static QIcon LoadNamedIcon(std::string_view name, const QString& dir);
static QList<QPixmap> m_platforms;
static QList<QPixmap> m_countries;
static QList<QPixmap> m_misc;
static bool m_svg_supported;
static QList<QIcon> m_platforms;
static QList<QIcon> m_countries;
static QList<QIcon> m_misc;
};

View File

@ -156,41 +156,41 @@ void ToolBar::UpdatePausePlayButtonState(const bool playing_state)
{
disconnect(m_pause_play_action, nullptr, nullptr, nullptr);
m_pause_play_action->setText(tr("Pause"));
m_pause_play_action->setIcon(Resources::GetScaledThemeIcon("pause"));
m_pause_play_action->setIcon(Resources::GetThemeIcon("pause"));
connect(m_pause_play_action, &QAction::triggered, this, &ToolBar::PausePressed);
}
else
{
disconnect(m_pause_play_action, nullptr, nullptr, nullptr);
m_pause_play_action->setText(tr("Play"));
m_pause_play_action->setIcon(Resources::GetScaledThemeIcon("play"));
m_pause_play_action->setIcon(Resources::GetThemeIcon("play"));
connect(m_pause_play_action, &QAction::triggered, this, &ToolBar::PlayPressed);
}
}
void ToolBar::UpdateIcons()
{
m_step_action->setIcon(Resources::GetScaledThemeIcon("debugger_step_in"));
m_step_over_action->setIcon(Resources::GetScaledThemeIcon("debugger_step_over"));
m_step_out_action->setIcon(Resources::GetScaledThemeIcon("debugger_step_out"));
m_skip_action->setIcon(Resources::GetScaledThemeIcon("debugger_skip"));
m_show_pc_action->setIcon(Resources::GetScaledThemeIcon("debugger_show_pc"));
m_set_pc_action->setIcon(Resources::GetScaledThemeIcon("debugger_set_pc"));
m_step_action->setIcon(Resources::GetThemeIcon("debugger_step_in"));
m_step_over_action->setIcon(Resources::GetThemeIcon("debugger_step_over"));
m_step_out_action->setIcon(Resources::GetThemeIcon("debugger_step_out"));
m_skip_action->setIcon(Resources::GetThemeIcon("debugger_skip"));
m_show_pc_action->setIcon(Resources::GetThemeIcon("debugger_show_pc"));
m_set_pc_action->setIcon(Resources::GetThemeIcon("debugger_set_pc"));
m_open_action->setIcon(Resources::GetScaledThemeIcon("open"));
m_refresh_action->setIcon(Resources::GetScaledThemeIcon("refresh"));
m_open_action->setIcon(Resources::GetThemeIcon("open"));
m_refresh_action->setIcon(Resources::GetThemeIcon("refresh"));
const Core::State state = Core::GetState();
const bool playing = state != Core::State::Uninitialized && state != Core::State::Paused;
if (!playing)
m_pause_play_action->setIcon(Resources::GetScaledThemeIcon("play"));
m_pause_play_action->setIcon(Resources::GetThemeIcon("play"));
else
m_pause_play_action->setIcon(Resources::GetScaledThemeIcon("pause"));
m_pause_play_action->setIcon(Resources::GetThemeIcon("pause"));
m_stop_action->setIcon(Resources::GetScaledThemeIcon("stop"));
m_fullscreen_action->setIcon(Resources::GetScaledThemeIcon("fullscreen"));
m_screenshot_action->setIcon(Resources::GetScaledThemeIcon("screenshot"));
m_config_action->setIcon(Resources::GetScaledThemeIcon("config"));
m_controllers_action->setIcon(Resources::GetScaledThemeIcon("classic"));
m_graphics_action->setIcon(Resources::GetScaledThemeIcon("graphics"));
m_stop_action->setIcon(Resources::GetThemeIcon("stop"));
m_fullscreen_action->setIcon(Resources::GetThemeIcon("fullscreen"));
m_screenshot_action->setIcon(Resources::GetThemeIcon("screenshot"));
m_config_action->setIcon(Resources::GetThemeIcon("config"));
m_controllers_action->setIcon(Resources::GetThemeIcon("classic"));
m_graphics_action->setIcon(Resources::GetThemeIcon("graphics"));
}