121 lines
3.7 KiB
C++
121 lines
3.7 KiB
C++
#include "BuildingIconCache.h"
|
|
|
|
#include <QFile>
|
|
#include <QGuiApplication>
|
|
#include <QPainter>
|
|
#include <QRegularExpression>
|
|
#include <QSvgRenderer>
|
|
|
|
namespace
|
|
{
|
|
|
|
// Chip background color for a build button the player cannot afford
|
|
// (REQ-UI-BUILD-DISABLED). Part of the icon rather than of any widget's palette, so it
|
|
// lives with the recoloring step.
|
|
const char* const kGreyFill = "fill=\"#5f636e\"";
|
|
|
|
} // namespace
|
|
|
|
|
|
BuildingIconCache::BuildingIconCache(const QString& iconDir)
|
|
: m_iconDir(iconDir)
|
|
{
|
|
}
|
|
|
|
const QByteArray& BuildingIconCache::getSvg(const std::string& iconName)
|
|
{
|
|
const std::map<std::string, QByteArray>::const_iterator cached =
|
|
m_svgByName.find(iconName);
|
|
if (cached != m_svgByName.end())
|
|
{
|
|
return cached->second;
|
|
}
|
|
|
|
// An absent or unreadable file caches an empty byte array so it is not retried;
|
|
// a missing icon is not an error (REQ-UI-BUILD-ICON).
|
|
QByteArray svg;
|
|
QFile file(m_iconDir + "/" + QString::fromStdString(iconName) + ".svg");
|
|
if (file.open(QIODevice::ReadOnly))
|
|
{
|
|
svg = file.readAll();
|
|
}
|
|
return m_svgByName.emplace(iconName, std::move(svg)).first->second;
|
|
}
|
|
|
|
const QByteArray& BuildingIconCache::getGreySvg(const std::string& iconName)
|
|
{
|
|
const std::map<std::string, QByteArray>::const_iterator cached =
|
|
m_greySvgByName.find(iconName);
|
|
if (cached != m_greySvgByName.end())
|
|
{
|
|
return cached->second;
|
|
}
|
|
|
|
// Recolor only the chip background: the first "#rrggbb" fill in the file is the
|
|
// rounded background rect; the white glyph uses fill="none" and is left alone.
|
|
const QByteArray& svg = getSvg(iconName);
|
|
QByteArray greyed = svg;
|
|
if (!svg.isEmpty())
|
|
{
|
|
QString text = QString::fromUtf8(svg);
|
|
static const QRegularExpression fillPattern(
|
|
QStringLiteral("fill=\"#[0-9a-fA-F]{6}\""));
|
|
const QRegularExpressionMatch match = fillPattern.match(text);
|
|
if (match.hasMatch())
|
|
{
|
|
text.replace(match.capturedStart(), match.capturedLength(),
|
|
QLatin1String(kGreyFill));
|
|
}
|
|
greyed = text.toUtf8();
|
|
}
|
|
return m_greySvgByName.emplace(iconName, std::move(greyed)).first->second;
|
|
}
|
|
|
|
bool BuildingIconCache::hasIcon(const std::string& iconName)
|
|
{
|
|
return !getSvg(iconName).isEmpty();
|
|
}
|
|
|
|
QPixmap BuildingIconCache::getChip(const std::string& iconName, int sizePx)
|
|
{
|
|
return getPixmap(iconName, getSvg(iconName), sizePx);
|
|
}
|
|
|
|
QPixmap BuildingIconCache::getGreyChip(const std::string& iconName, int sizePx)
|
|
{
|
|
return getPixmap("grey:" + iconName, getGreySvg(iconName), sizePx);
|
|
}
|
|
|
|
QPixmap BuildingIconCache::getPixmap(const std::string& cacheKey,
|
|
const QByteArray& svg, int sizePx)
|
|
{
|
|
if (sizePx <= 0)
|
|
{
|
|
return QPixmap();
|
|
}
|
|
|
|
const std::pair<std::string, int> key(cacheKey, sizePx);
|
|
const std::map<std::pair<std::string, int>, QPixmap>::const_iterator cached =
|
|
m_pixmapCache.find(key);
|
|
if (cached != m_pixmapCache.end())
|
|
{
|
|
return cached->second;
|
|
}
|
|
|
|
QPixmap pixmap;
|
|
if (!svg.isEmpty())
|
|
{
|
|
// Rasterized straight at its on-screen size times the device pixel ratio, so it
|
|
// stays crisp without a downscale step.
|
|
const qreal dpr = qApp ? qApp->devicePixelRatio() : 1.0;
|
|
QSvgRenderer renderer(svg);
|
|
pixmap = QPixmap(static_cast<int>(sizePx * dpr), static_cast<int>(sizePx * dpr));
|
|
pixmap.setDevicePixelRatio(dpr);
|
|
pixmap.fill(Qt::transparent);
|
|
QPainter painter(&pixmap);
|
|
painter.setRenderHint(QPainter::Antialiasing, true);
|
|
renderer.render(&painter);
|
|
}
|
|
return m_pixmapCache.emplace(key, std::move(pixmap)).first->second;
|
|
}
|