Render produced-item icons in recipe dialog and game world

Implements REQ-UI-ITEM-ICON and REQ-UI-RECIPE-ICON. Adds an optional
recipes.toml `icon` field (an item id; defaults to the recipe's first
output). New ItemIconCache loads per-item SVGs from icons/items and
caches pixmaps per target pixel size, shared by:
- the Miner/Assembler recipe-selection dialog, which now shows icon-only
  option buttons (name-caption fallback when no icon file exists), and
- GameWorldView belt/port item rendering via a shared drawWorldItem
  helper (colored-square fallback preserved).

Shipyard schematic dialog is unchanged. No icon art is shipped; every
path falls back cleanly when an item has no SVG.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y7N59FsLA5e2kuVdqe4Uhc
This commit is contained in:
2026-07-23 14:43:51 +02:00
parent 1cc4175071
commit 6a317a52b1
11 changed files with 253 additions and 26 deletions

45
src/ui/ItemIconCache.h Normal file
View File

@@ -0,0 +1,45 @@
#pragma once
#include <map>
#include <string>
#include <utility>
#include <QByteArray>
#include <QPixmap>
#include <QString>
// Rasterizes and caches per-item icon SVGs (REQ-UI-ITEM-ICON). Item icons are
// self-contained, full-color SVGs loaded from a directory, one file per item type
// named after the item's id (e.g. "iron_ore.svg"). Shared by the recipe-selection
// dialog (REQ-UI-RECIPE-ICON) and the game world's belt/port item rendering so the
// rasterization is not duplicated.
//
// A missing icon file is not an error: hasIcon() returns false for it and the caller
// falls back (a colored square in the world, a name caption in the dialog).
class ItemIconCache
{
public:
// iconDir is the directory holding the "<item_id>.svg" icon files
// (typically "<configDir>/../icons/items").
explicit ItemIconCache(const QString& iconDir);
// True if an icon SVG file exists for the given item id. Loads the file's bytes
// on first query and remembers the result (including absence) so repeated calls
// are cheap.
bool hasIcon(const std::string& itemId);
// Returns the item's icon rasterized to a transparent sizePx*sizePx pixmap,
// cached per (item id, size) so it is rendered once and reused across frames and
// only re-rasterized when the target size changes (REQ-UI-ITEM-ICON). Returns a
// null pixmap if the item has no icon file (callers should gate on hasIcon()).
QPixmap getPixmap(const std::string& itemId, int sizePx);
private:
// Returns the raw SVG bytes for an item id, loading and caching them on first
// access. An absent file caches an empty QByteArray so it is not retried.
const QByteArray& getSvg(const std::string& itemId);
QString m_iconDir;
std::map<std::string, QByteArray> m_svgById;
std::map<std::pair<std::string, int>, QPixmap> m_pixmapCache;
};