color item icon backgrounds in the UI from visuals.toml
This commit is contained in:
@@ -2,10 +2,26 @@
|
||||
|
||||
#include <QFile>
|
||||
#include <QPainter>
|
||||
#include <QRectF>
|
||||
#include <QSvgRenderer>
|
||||
|
||||
ItemIconCache::ItemIconCache(const QString& iconDir)
|
||||
#include "VisualsConfig.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// How far the icon is inset within the item's colored square, as a fraction of the
|
||||
// square's size on each side (REQ-UI-ITEM-ICON). The inset is what keeps a frame of the
|
||||
// square's color visible all around the icon: each icon's viewBox is cropped tight to
|
||||
// its artwork, so an icon drawn at the full rect would cover the square entirely.
|
||||
const double kIconInsetFraction = 0.15;
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
ItemIconCache::ItemIconCache(const QString& iconDir, const VisualsConfig* visuals)
|
||||
: m_iconDir(iconDir)
|
||||
, m_visuals(visuals)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -34,14 +50,59 @@ bool ItemIconCache::hasIcon(const std::string& itemId)
|
||||
return !getSvg(itemId).isEmpty();
|
||||
}
|
||||
|
||||
void ItemIconCache::paintItem(QPainter& painter, const QRectF& rect,
|
||||
const std::string& itemId)
|
||||
{
|
||||
// The colored square from visuals.toml backs every item, icon or not: it is what
|
||||
// gives the item contrast against the tile beneath it in the world, and its outline
|
||||
// is what separates neighbouring items where they overlap on a belt (REQ-GW-TILE-SIZE,
|
||||
// REQ-UI-ITEM-ICON).
|
||||
if (m_visuals != nullptr)
|
||||
{
|
||||
const std::map<std::string, ItemVisuals>::const_iterator it =
|
||||
m_visuals->items.find(itemId);
|
||||
if (it != m_visuals->items.end())
|
||||
{
|
||||
painter.fillRect(rect, it->second.fill);
|
||||
painter.setPen(QPen(it->second.outline, 1));
|
||||
painter.setBrush(Qt::NoBrush);
|
||||
painter.drawRect(rect);
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasIcon(itemId)) { return; }
|
||||
|
||||
// The icon goes on top, inset so a frame of the square's color stays visible all
|
||||
// around it. It is rasterized once at the inset pixel size and cached, so this is a
|
||||
// plain pixmap blit per frame.
|
||||
const double inset = kIconInsetFraction * rect.width();
|
||||
const QRectF iconRect = rect.adjusted(inset, inset, -inset, -inset);
|
||||
|
||||
int sizePx = qRound(iconRect.width());
|
||||
if (sizePx < 1) { sizePx = 1; }
|
||||
painter.drawPixmap(iconRect, getPixmap(itemId, sizePx),
|
||||
QRectF(0, 0, sizePx, sizePx));
|
||||
}
|
||||
|
||||
QPixmap ItemIconCache::getSquarePixmap(const std::string& itemId, int sizePx)
|
||||
{
|
||||
return getPixmap("square:" + itemId, itemId, sizePx, true);
|
||||
}
|
||||
|
||||
QPixmap ItemIconCache::getPixmap(const std::string& itemId, int sizePx)
|
||||
{
|
||||
return getPixmap(itemId, itemId, sizePx, false);
|
||||
}
|
||||
|
||||
QPixmap ItemIconCache::getPixmap(const std::string& cacheKey, const std::string& itemId,
|
||||
int sizePx, bool withSquare)
|
||||
{
|
||||
if (sizePx <= 0)
|
||||
{
|
||||
return QPixmap();
|
||||
}
|
||||
|
||||
const std::pair<std::string, int> key(itemId, sizePx);
|
||||
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())
|
||||
@@ -49,16 +110,45 @@ QPixmap ItemIconCache::getPixmap(const std::string& itemId, int sizePx)
|
||||
return cached->second;
|
||||
}
|
||||
|
||||
const QByteArray& svg = getSvg(itemId);
|
||||
QPixmap pixmap;
|
||||
if (!svg.isEmpty())
|
||||
if (withSquare)
|
||||
{
|
||||
QSvgRenderer renderer(svg);
|
||||
pixmap = QPixmap(sizePx, sizePx);
|
||||
pixmap.fill(Qt::transparent);
|
||||
QPainter painter(&pixmap);
|
||||
painter.setRenderHint(QPainter::Antialiasing, true);
|
||||
renderer.render(&painter);
|
||||
// Null unless the item has something to draw -- a square, an icon, or both --
|
||||
// so a caller with nothing to show can fall back to text (REQ-UI-RECIPE-ICON).
|
||||
const bool hasSquare = m_visuals != nullptr
|
||||
&& m_visuals->items.find(itemId) != m_visuals->items.end();
|
||||
if (hasSquare || hasIcon(itemId))
|
||||
{
|
||||
pixmap = QPixmap(sizePx, sizePx);
|
||||
pixmap.fill(Qt::transparent);
|
||||
QPainter painter(&pixmap);
|
||||
// No antialiasing: the square's edges are axis-aligned and land on pixel
|
||||
// boundaries, and smoothing a 1-pixel outline only blurs it. The icon is
|
||||
// blitted into a slightly smaller rect than it was rasterized at, which is
|
||||
// what the smooth transform is for.
|
||||
painter.setRenderHint(QPainter::SmoothPixmapTransform, true);
|
||||
// One pixel short of the pixmap so the square's right and bottom edges land
|
||||
// inside it rather than on its border.
|
||||
paintItem(painter, QRectF(0, 0, sizePx - 1, sizePx - 1), itemId);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const QByteArray& svg = getSvg(itemId);
|
||||
if (!svg.isEmpty())
|
||||
{
|
||||
QSvgRenderer renderer(svg);
|
||||
pixmap = QPixmap(sizePx, sizePx);
|
||||
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;
|
||||
}
|
||||
|
||||
void ItemIconCache::clearPixmapCache()
|
||||
{
|
||||
m_pixmapCache.clear();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user