diff --git a/CMakeLists.txt b/CMakeLists.txt
index e51d757..d5e3cb9 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -24,7 +24,7 @@ set(CMAKE_BUILD_TYPE_INIT "Release")
# Qt ---------------------------------------------------------------------------
-find_package(Qt5 COMPONENTS Widgets Network Multimedia Charts REQUIRED)
+find_package(Qt5 COMPONENTS Widgets Network Multimedia Charts Svg REQUIRED)
if(Qt5Widgets_FOUND)
message(STATUS "Found Qt ${Qt5Widgets_VERSION_STRING}")
@@ -64,6 +64,7 @@ function(COPY_QT_BINARIES TARGET_DIR IS_DEBUG)
configure_file("${QT_BINARY_DIR}/Qt5Network${SUFFIX}.dll" "${TARGET_DIR}/Qt5Network${SUFFIX}.dll" COPYONLY)
configure_file("${QT_BINARY_DIR}/Qt5Widgets${SUFFIX}.dll" "${TARGET_DIR}/Qt5Widgets${SUFFIX}.dll" COPYONLY)
configure_file("${QT_BINARY_DIR}/Qt5Multimedia${SUFFIX}.dll" "${TARGET_DIR}/Qt5Multimedia${SUFFIX}.dll" COPYONLY)
+ configure_file("${QT_BINARY_DIR}/Qt5Svg${SUFFIX}.dll" "${TARGET_DIR}/Qt5Svg${SUFFIX}.dll" COPYONLY)
endfunction(COPY_QT_BINARIES)
diff --git a/bin/app/data/icons/buildings/assembler.svg b/bin/app/data/icons/buildings/assembler.svg
new file mode 100644
index 0000000..8fcf9f2
--- /dev/null
+++ b/bin/app/data/icons/buildings/assembler.svg
@@ -0,0 +1,6 @@
+
diff --git a/bin/app/data/icons/buildings/belt.svg b/bin/app/data/icons/buildings/belt.svg
new file mode 100644
index 0000000..9739134
--- /dev/null
+++ b/bin/app/data/icons/buildings/belt.svg
@@ -0,0 +1,6 @@
+
diff --git a/bin/app/data/icons/buildings/deconstruct.svg b/bin/app/data/icons/buildings/deconstruct.svg
new file mode 100644
index 0000000..a07a642
--- /dev/null
+++ b/bin/app/data/icons/buildings/deconstruct.svg
@@ -0,0 +1,6 @@
+
diff --git a/bin/app/data/icons/buildings/miner.svg b/bin/app/data/icons/buildings/miner.svg
new file mode 100644
index 0000000..30123a2
--- /dev/null
+++ b/bin/app/data/icons/buildings/miner.svg
@@ -0,0 +1,6 @@
+
diff --git a/bin/app/data/icons/buildings/reprocessing_plant.svg b/bin/app/data/icons/buildings/reprocessing_plant.svg
new file mode 100644
index 0000000..f5024ba
--- /dev/null
+++ b/bin/app/data/icons/buildings/reprocessing_plant.svg
@@ -0,0 +1,6 @@
+
diff --git a/bin/app/data/icons/buildings/salvage_bay.svg b/bin/app/data/icons/buildings/salvage_bay.svg
new file mode 100644
index 0000000..844cbcd
--- /dev/null
+++ b/bin/app/data/icons/buildings/salvage_bay.svg
@@ -0,0 +1,6 @@
+
diff --git a/bin/app/data/icons/buildings/shipyard.svg b/bin/app/data/icons/buildings/shipyard.svg
new file mode 100644
index 0000000..cf8043a
--- /dev/null
+++ b/bin/app/data/icons/buildings/shipyard.svg
@@ -0,0 +1,6 @@
+
diff --git a/bin/app/data/icons/buildings/smelter.svg b/bin/app/data/icons/buildings/smelter.svg
new file mode 100644
index 0000000..3697779
--- /dev/null
+++ b/bin/app/data/icons/buildings/smelter.svg
@@ -0,0 +1,6 @@
+
diff --git a/bin/app/data/icons/buildings/splitter.svg b/bin/app/data/icons/buildings/splitter.svg
new file mode 100644
index 0000000..9ab41fa
--- /dev/null
+++ b/bin/app/data/icons/buildings/splitter.svg
@@ -0,0 +1,6 @@
+
diff --git a/bin/app/data/icons/buildings/tunnel_entry.svg b/bin/app/data/icons/buildings/tunnel_entry.svg
new file mode 100644
index 0000000..b31390d
--- /dev/null
+++ b/bin/app/data/icons/buildings/tunnel_entry.svg
@@ -0,0 +1,6 @@
+
diff --git a/docs/requirements.md b/docs/requirements.md
index 7978a68..ee2bacd 100644
--- a/docs/requirements.md
+++ b/docs/requirements.md
@@ -553,8 +553,9 @@ The screen is divided into two columns: a main column (75% width) containing the
- REQ-UI-BUILD-GRID: All placeable building types are shown as a flat grid of buttons with no grouping. Tunnel Entry and Tunnel Exit share a single **Tunnel** button (REQ-BLD-TUNNEL-MODE) rather than one button each.
- REQ-UI-BUILD-COST: Each button caption shows the building name and its building block cost, e.g. "Belt: 2 Blocks".
+- REQ-UI-BUILD-ICON: Each build button shows an icon alongside its caption. Icons are SVG files loaded at runtime from `data/icons/buildings/` (a sibling of the config directory, read the same way as `visuals.toml`), one file per button named after the building's id (e.g. `belt.svg`, `reprocessing_plant.svg`). The shared Tunnel button (REQ-UI-BUILD-GRID) uses `tunnel_entry.svg`; the Deconstruct button (REQ-UI-DECONSTRUCT-BUTTON) uses `deconstruct.svg`. Each icon is a rounded colored "chip" bearing a white line glyph, the chip color following the building's fill color in `visuals.toml`. A missing icon file leaves the button with its caption and no icon; it is not an error.
- REQ-UI-BUILD-TOOLTIP: Each building-type button shows a hover tooltip with the descriptive text defined for that building type in `buildings.toml` (the optional per-building tooltip field). This tooltip is distinct from the recipe/schematic selection tooltip (REQ-UI-SELECT-TOOLTIP). If a building type defines no tooltip text, its button shows no tooltip. The Deconstruct button (REQ-UI-DECONSTRUCT-BUTTON) is not a building type and has no config-defined tooltip.
-- REQ-UI-BUILD-DISABLED: Buttons for buildings the player cannot currently afford are shown as disabled.
+- REQ-UI-BUILD-DISABLED: Buttons for buildings the player cannot currently afford are shown as disabled. A disabled button's icon (REQ-UI-BUILD-ICON) is rendered in a greyed variant, with its colored chip background recolored grey while the white glyph is retained.
- REQ-UI-DECONSTRUCT-BUTTON: A dedicated **Deconstruct** button is shown in the build button grid. Clicking it toggles deconstruct mode on and off, equivalent to the Q deconstruct toggle (REQ-UI-HOTKEYS). The button is shown in a visually active/pressed state while deconstruct mode is active.
### Blueprint Panel
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 2f71a4e..929bc42 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -117,6 +117,7 @@ target_link_libraries(${TARGET_UI_NAME}
Qt5::Network
Qt5::Multimedia
Qt5::Charts
+ Qt5::Svg
)
target_compile_definitions(${TARGET_UI_NAME} PRIVATE TOML_FLOAT_CHARCONV=0)
diff --git a/src/ui/BuildButtonGrid.cpp b/src/ui/BuildButtonGrid.cpp
index 92aeef8..465866a 100644
--- a/src/ui/BuildButtonGrid.cpp
+++ b/src/ui/BuildButtonGrid.cpp
@@ -2,9 +2,18 @@
#include
+#include
+#include
#include
+#include
+#include
+#include
#include
+#include
#include
+#include
+#include
+#include
#include "BuildingType.h"
#include "BuildingTypeSelectedEvent.h"
@@ -14,11 +23,60 @@
#include "ExitBuilderModeRequestedEvent.h"
#include "Simulation.h"
+namespace
+{
+ // Pixel size the SVG chips are rasterized at; downscaled to the button icon size.
+ const int kIconRenderSize = 64;
+ const QSize kIconSize(28, 28);
-BuildButtonGrid::BuildButtonGrid(Simulation* sim, const GameConfig* config, QWidget* parent)
+ QPixmap renderChip(const QByteArray& svg)
+ {
+ QSvgRenderer renderer(svg);
+ QPixmap pixmap(kIconRenderSize, kIconRenderSize);
+ pixmap.fill(Qt::transparent);
+ QPainter painter(&pixmap);
+ renderer.render(&painter);
+ return pixmap;
+ }
+
+ // Builds a build-button icon from a ".svg" chip file. The returned QIcon also
+ // carries a Disabled-mode pixmap whose chip background is recolored grey, so an
+ // unaffordable (disabled) button shows the grey variant automatically
+ // (REQ-UI-BUILD-DISABLED) without any extra work in updateAffordability().
+ QIcon loadBuildingIcon(const QString& path)
+ {
+ QFile file(path);
+ if (!file.open(QIODevice::ReadOnly))
+ {
+ return QIcon();
+ }
+ const QByteArray svg = file.readAll();
+
+ QIcon icon;
+ icon.addPixmap(renderChip(svg), QIcon::Normal);
+
+ // 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.
+ QString greyed = QString::fromUtf8(svg);
+ static const QRegularExpression fillPattern(QStringLiteral("fill=\"#[0-9a-fA-F]{6}\""));
+ const QRegularExpressionMatch match = fillPattern.match(greyed);
+ if (match.hasMatch())
+ {
+ greyed.replace(match.capturedStart(), match.capturedLength(),
+ QStringLiteral("fill=\"#5f636e\""));
+ }
+ icon.addPixmap(renderChip(greyed.toUtf8()), QIcon::Disabled);
+ return icon;
+ }
+}
+
+
+BuildButtonGrid::BuildButtonGrid(Simulation* sim, const GameConfig* config,
+ const std::string& iconDir, QWidget* parent)
: QWidget(parent)
, m_sim(sim)
, m_config(config)
+ , m_iconDir(iconDir)
{
QGridLayout* layout = new QGridLayout(this);
layout->setSpacing(4);
@@ -53,6 +111,12 @@ BuildButtonGrid::BuildButtonGrid(Simulation* sim, const GameConfig* config, QWid
QPushButton* btn = new QPushButton(label, this);
btn->setCheckable(true);
btn->setFixedHeight(48);
+ // Icon file name matches the building id (REQ-UI-BUILD-GRID); Tunnel Entry's
+ // "tunnel_entry.svg" serves the shared Tunnel button.
+ const QString iconPath = QString::fromStdString(m_iconDir) + "/"
+ + QString::fromStdString(def.id) + ".svg";
+ btn->setIcon(loadBuildingIcon(iconPath));
+ btn->setIconSize(kIconSize);
if (def.tooltip)
{
btn->setToolTip(QString::fromStdString(*def.tooltip));
@@ -76,6 +140,9 @@ BuildButtonGrid::BuildButtonGrid(Simulation* sim, const GameConfig* config, QWid
m_deconstructButton = new QPushButton(tr("Deconstruct"), this);
m_deconstructButton->setCheckable(true);
m_deconstructButton->setFixedHeight(48);
+ m_deconstructButton->setIcon(loadBuildingIcon(
+ QString::fromStdString(m_iconDir) + "/deconstruct.svg"));
+ m_deconstructButton->setIconSize(kIconSize);
layout->addWidget(m_deconstructButton, row, col);
connect(m_deconstructButton, &QPushButton::clicked, this, [this]() {
EventManager::getInstance()->sendEventImmediately(
diff --git a/src/ui/BuildButtonGrid.h b/src/ui/BuildButtonGrid.h
index b9578dc..3cc4c62 100644
--- a/src/ui/BuildButtonGrid.h
+++ b/src/ui/BuildButtonGrid.h
@@ -2,6 +2,7 @@
#include