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 #include +#include #include #include @@ -28,7 +29,10 @@ class BuildButtonGrid : public QWidget, Q_OBJECT public: - BuildButtonGrid(Simulation* sim, const GameConfig* config, QWidget* parent = nullptr); + // iconDir is the directory holding the per-building ".svg" chip icons + // (REQ-UI-BUILD-GRID); read from disk at runtime, like the config files. + BuildButtonGrid(Simulation* sim, const GameConfig* config, + const std::string& iconDir, QWidget* parent = nullptr); ~BuildButtonGrid() override; void clearActiveButton(); @@ -55,6 +59,7 @@ private slots: private: Simulation* m_sim; const GameConfig* m_config; + std::string m_iconDir; std::vector m_types; std::vector m_buttons; std::map m_costs; diff --git a/src/ui/MainWindow.cpp b/src/ui/MainWindow.cpp index c6c53ff..14fe5f5 100644 --- a/src/ui/MainWindow.cpp +++ b/src/ui/MainWindow.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -51,8 +52,13 @@ MainWindow::MainWindow(Simulation* sim, const std::string& configDir, sideLayout->setContentsMargins(1, 1, 1, 1); sideLayout->setSpacing(1); + // Building icons live alongside the config (a sibling of the config dir), read + // from disk at runtime the same way visuals.toml is. + const std::string iconDir = QDir::cleanPath( + QString::fromStdString(m_configDir) + "/../icons/buildings").toStdString(); + m_selectedBuildingPanel = new SelectedBuildingPanel(sim, &sim->getConfig(), m_sidePanel); - m_buildButtonGrid = new BuildButtonGrid(sim, &sim->getConfig(), m_sidePanel); + m_buildButtonGrid = new BuildButtonGrid(sim, &sim->getConfig(), iconDir, m_sidePanel); m_blueprintPanel = new BlueprintPanel(sim, &sim->getConfig(), m_sidePanel); sideLayout->addWidget(m_selectedBuildingPanel, 1);