diff options
author | Thorbjørn Lindeijer <bjorn@lindeijer.nl> | 2025-04-09 11:39:12 +0200 |
---|---|---|
committer | Thorbjørn Lindeijer <bjorn@lindeijer.nl> | 2025-04-25 14:05:30 +0000 |
commit | cf6e213f060866ac5da46d4c516365b3cbedc7b3 (patch) | |
tree | c46f56caf305a9b166183c4f66a76e03d1d990d7 | |
parent | 6536aff2d24a48a4e91d31eb35971e4b788ec3b0 (diff) | |
download | mana-cf6e213f060866ac5da46d4c516365b3cbedc7b3.tar.gz mana-cf6e213f060866ac5da46d4c516365b3cbedc7b3.tar.bz2 mana-cf6e213f060866ac5da46d4c516365b3cbedc7b3.tar.xz mana-cf6e213f060866ac5da46d4c516365b3cbedc7b3.zip |
GUI: Added support for defining named icons to the theme
Now we can define icon names for each equipment box and then look up the
icon image through the theme, enabling some shared control over
equipment slot icons between the game data and GUI theme.
The icon for an equipment slot is also no longer rendered when the slot
has an item in it.
Removed the needless storing of equipment box positions and images in
the EquipmentWindow, because this information is readily available from
the InventoryHandler and the images are already loaded by the Theme.
-rw-r--r-- | data/graphics/gui/jewelry/theme.xml | 11 | ||||
-rw-r--r-- | data/graphics/gui/theme.xml | 11 | ||||
-rw-r--r-- | src/gui/equipmentwindow.cpp | 75 | ||||
-rw-r--r-- | src/gui/equipmentwindow.h | 20 | ||||
-rw-r--r-- | src/gui/itemamountwindow.cpp | 4 | ||||
-rw-r--r-- | src/net/inventoryhandler.h | 44 | ||||
-rw-r--r-- | src/net/manaserv/inventoryhandler.cpp | 17 | ||||
-rw-r--r-- | src/net/manaserv/inventoryhandler.h | 12 | ||||
-rw-r--r-- | src/net/tmwa/inventoryhandler.h | 1 | ||||
-rw-r--r-- | src/position.h | 2 | ||||
-rw-r--r-- | src/resources/theme.cpp | 55 | ||||
-rw-r--r-- | src/resources/theme.h | 4 |
12 files changed, 148 insertions, 108 deletions
diff --git a/data/graphics/gui/jewelry/theme.xml b/data/graphics/gui/jewelry/theme.xml index 29e421ac..fd20f133 100644 --- a/data/graphics/gui/jewelry/theme.xml +++ b/data/graphics/gui/jewelry/theme.xml @@ -383,4 +383,15 @@ <img src="window.png" x="175" y="114" width="38" height="38" /> </state> </skin> + + <icon name="equip-box-ammo" src="equipmentbox.png" x="128" y="32" width="32" height="32" /> + <icon name="equip-box-chest" src="equipmentbox.png" x="0" y="0" width="32" height="32" /> + <icon name="equip-box-feet" src="equipmentbox.png" x="128" y="0" width="32" height="32" /> + <icon name="equip-box-hands" src="equipmentbox.png" x="32" y="0" width="32" height="32" /> + <icon name="equip-box-head" src="equipmentbox.png" x="64" y="0" width="32" height="32" /> + <icon name="equip-box-legs" src="equipmentbox.png" x="96" y="0" width="32" height="32" /> + <icon name="equip-box-neck" src="equipmentbox.png" x="32" y="32" width="32" height="32" /> + <icon name="equip-box-ring" src="equipmentbox.png" x="0" y="32" width="32" height="32" /> + <icon name="equip-box-shield" src="equipmentbox.png" x="96" y="32" width="32" height="32" /> + <icon name="equip-box-weapon" src="equipmentbox.png" x="64" y="32" width="32" height="32" /> </theme> diff --git a/data/graphics/gui/theme.xml b/data/graphics/gui/theme.xml index df388380..8bb95f80 100644 --- a/data/graphics/gui/theme.xml +++ b/data/graphics/gui/theme.xml @@ -299,4 +299,15 @@ <img src="selection.png" /> </state> </skin> + + <icon name="equip-box-ammo" src="equip-box-ammo.png" /> + <icon name="equip-box-chest" src="equip-box-chest.png" /> + <icon name="equip-box-feet" src="equip-box-feet.png" /> + <icon name="equip-box-hands" src="equip-box-hands.png" /> + <icon name="equip-box-head" src="equip-box-head.png" /> + <icon name="equip-box-legs" src="equip-box-legs.png" /> + <icon name="equip-box-neck" src="equip-box-neck.png" /> + <icon name="equip-box-ring" src="equip-box-ring.png" /> + <icon name="equip-box-shield" src="equip-box-shield.png" /> + <icon name="equip-box-weapon" src="equip-box-weapon.png" /> </theme> diff --git a/src/gui/equipmentwindow.cpp b/src/gui/equipmentwindow.cpp index 4d99c482..b9d3b0fd 100644 --- a/src/gui/equipmentwindow.cpp +++ b/src/gui/equipmentwindow.cpp @@ -74,28 +74,6 @@ EquipmentWindow::EquipmentWindow(Equipment *equipment): add(playerBox); add(mUnequip); - - loadEquipBoxes(); -} - -void EquipmentWindow::loadEquipBoxes() -{ - mBoxes.resize(mEquipment->getSlotNumber()); - - for (size_t i = 0; i < mBoxes.size(); ++i) - { - auto &box = mBoxes[i]; - - Position boxPosition = Net::getInventoryHandler()->getBoxPosition(i); - box.posX = boxPosition.x + getPadding(); - box.posY = boxPosition.y + getTitleBarHeight(); - - const std::string &backgroundFile = - Net::getInventoryHandler()->getBoxBackground(i); - - if (!backgroundFile.empty()) - box.backgroundImage = Theme::getImageFromTheme(backgroundFile); - } } EquipmentWindow::~EquipmentWindow() @@ -107,50 +85,51 @@ void EquipmentWindow::draw(gcn::Graphics *graphics) { Window::draw(graphics); - // Draw equipment boxes auto *g = static_cast<Graphics*>(graphics); - auto &boxSkin = gui->getTheme()->getSkin(SkinType::EquipmentBox); + auto theme = gui->getTheme(); + auto &boxSkin = theme->getSkin(SkinType::EquipmentBox); - for (size_t i = 0; i < mBoxes.size(); i++) + // Draw equipment boxes + const int boxCount = mEquipment->getSlotNumber(); + for (int i = 0; i < boxCount; ++i) { - const auto &box = mBoxes[i]; + Position boxPos = Net::getInventoryHandler()->getBoxPosition(i); + boxPos.x += getPadding(); + boxPos.y += getTitleBarHeight(); - WidgetState boxState(gcn::Rectangle(box.posX, box.posY, boxSkin.width, boxSkin.height)); - if (static_cast<int>(i) == mSelected) + WidgetState boxState(gcn::Rectangle(boxPos.x, boxPos.y, boxSkin.width, boxSkin.height)); + if (i == mSelected) boxState.flags |= STATE_SELECTED; boxSkin.draw(g, boxState); - // When there is a background image, draw it centered in the box: - if (box.backgroundImage) - { - int posX = box.posX - + (boxSkin.width - box.backgroundImage->getWidth()) / 2; - int posY = box.posY - + (boxSkin.height - box.backgroundImage->getHeight()) / 2; - g->drawImage(box.backgroundImage, posX, posY); - } - if (Item *item = mEquipment->getEquipment(i)) { if (Image *image = item->getImage()) { image->setAlpha(1.0f); g->drawImage(image, - box.posX + boxSkin.padding, - box.posY + boxSkin.padding); + boxPos.x + boxSkin.padding, + boxPos.y + boxSkin.padding); } if (i == TmwAthena::EQUIP_PROJECTILE_SLOT) { g->setColor(Theme::getThemeColor(Theme::TEXT)); graphics->drawText(toString(item->getQuantity()), - box.posX + (boxSkin.width / 2), - box.posY - getFont()->getHeight(), + boxPos.x + boxSkin.width / 2, + boxPos.y - getFont()->getHeight(), gcn::Graphics::CENTER); } } + else + { + auto &icon = Net::getInventoryHandler()->getBoxIcon(i); + if (!icon.empty()) + if (auto image = theme->getIcon(icon)) + g->drawImage(image, boxPos.x + boxSkin.padding, boxPos.y + boxSkin.padding); + } } } @@ -171,10 +150,16 @@ int EquipmentWindow::getBoxIndex(int x, int y) const { auto &boxSkin = gui->getTheme()->getSkin(SkinType::EquipmentBox); - for (size_t i = 0; i < mBoxes.size(); ++i) + // Translate coordinates to content area + const auto childrenArea = const_cast<EquipmentWindow*>(this)->getChildrenArea(); + x -= childrenArea.x; + y -= childrenArea.y; + + const int boxCount = mEquipment->getSlotNumber(); + for (int i = 0; i < boxCount; ++i) { - const auto &box = mBoxes[i]; - const gcn::Rectangle tRect(box.posX, box.posY, boxSkin.width, boxSkin.height); + const Position boxPos = Net::getInventoryHandler()->getBoxPosition(i); + const gcn::Rectangle tRect(boxPos.x, boxPos.y, boxSkin.width, boxSkin.height); if (tRect.isPointInRect(x, y)) return i; } diff --git a/src/gui/equipmentwindow.h b/src/gui/equipmentwindow.h index f46d1175..944ae99e 100644 --- a/src/gui/equipmentwindow.h +++ b/src/gui/equipmentwindow.h @@ -22,14 +22,11 @@ #pragma once #include "equipment.h" -#include "resources/image.h" #include "gui/widgets/window.h" #include <guichan/actionlistener.hpp> -#include <vector> - class Inventory; class Item; class ItemPopup; @@ -58,29 +55,12 @@ class EquipmentWindow : public Window, public gcn::ActionListener void mouseExited(gcn::MouseEvent &event) override; /** - * Loads the correct amount of displayed equip boxes. - */ - void loadEquipBoxes(); - - /** * Returns the current selected slot or -1 if none. */ int getSelected() const { return mSelected; } protected: - /** - * Equipment box. - */ - struct EquipBox - { - int posX = 0; - int posY = 0; - ResourceRef<Image> backgroundImage; - }; - - std::vector<EquipBox> mBoxes; /**< Equipment boxes. */ - int mSelected = -1; /**< Index of selected item. */ Equipment *mEquipment; diff --git a/src/gui/itemamountwindow.cpp b/src/gui/itemamountwindow.cpp index da5dc073..c986a529 100644 --- a/src/gui/itemamountwindow.cpp +++ b/src/gui/itemamountwindow.cpp @@ -21,6 +21,7 @@ #include "gui/itemamountwindow.h" +#include "inventory.h" #include "item.h" #include "keyboardconfig.h" @@ -34,9 +35,6 @@ #include "gui/widgets/slider.h" #include "gui/widgets/icon.h" -#include "net/inventoryhandler.h" -#include "net/net.h" - #include "utils/gettext.h" void ItemAmountWindow::finish(Item *item, int amount, Usage usage) diff --git a/src/net/inventoryhandler.h b/src/net/inventoryhandler.h index 323dec6c..995e54d9 100644 --- a/src/net/inventoryhandler.h +++ b/src/net/inventoryhandler.h @@ -22,16 +22,14 @@ #pragma once #include "equipment.h" -#include "inventory.h" -#include "item.h" #include "position.h" -#include <iosfwd> +#include <string> namespace Net { // Default positions of the boxes, 2nd dimension is X and Y respectively. -const int fallBackBoxesPosition[][2] = { +constexpr Position fallBackBoxPositions[] = { { 90, 40 }, // EQUIP_TORSO_SLOT { 8, 78 }, // EQUIP_GLOVES_SLOT { 70, 0 }, // EQUIP_HEAD_SLOT @@ -45,21 +43,21 @@ const int fallBackBoxesPosition[][2] = { { 129, 78 } // EQUIP_PROJECTILE_SLOT }; -const std::string fallBackBoxesBackground[] = { - "equip-box-chest.png", - "equip-box-hands.png", - "equip-box-head.png", - "equip-box-legs.png", - "equip-box-feet.png", - "equip-box-ring.png", - "equip-box-ring.png", - "equip-box-neck.png", - "equip-box-weapon.png", - "equip-box-shield.png", - "equip-box-ammo.png" +const std::string fallBackBoxIcons[] = { + "equip-box-chest", + "equip-box-hands", + "equip-box-head", + "equip-box-legs", + "equip-box-feet", + "equip-box-ring", + "equip-box-ring", + "equip-box-neck", + "equip-box-weapon", + "equip-box-shield", + "equip-box-ammo" }; -static const std::string empty = std::string(); +static const std::string empty; class InventoryHandler { @@ -80,17 +78,15 @@ class InventoryHandler virtual Position getBoxPosition(unsigned int slotIndex) const { - if (slotIndex < (sizeof(fallBackBoxesPosition) - / sizeof(fallBackBoxesPosition[0][0]))) - return Position(fallBackBoxesPosition[slotIndex][0], - fallBackBoxesPosition[slotIndex][1]); + if (slotIndex < sizeof(fallBackBoxPositions) / sizeof(fallBackBoxPositions[0])) + return fallBackBoxPositions[slotIndex]; return Position(0,0); } - virtual const std::string& getBoxBackground(unsigned int slotIndex) const + virtual const std::string& getBoxIcon(unsigned int slotIndex) const { - if (slotIndex < sizeof(fallBackBoxesBackground)) - return fallBackBoxesBackground[slotIndex]; + if (slotIndex < sizeof(fallBackBoxIcons) / sizeof(fallBackBoxIcons[0])) + return fallBackBoxIcons[slotIndex]; return empty; // The empty string } }; diff --git a/src/net/manaserv/inventoryhandler.cpp b/src/net/manaserv/inventoryhandler.cpp index e1dc5dea..58a495af 100644 --- a/src/net/manaserv/inventoryhandler.cpp +++ b/src/net/manaserv/inventoryhandler.cpp @@ -197,11 +197,10 @@ void EquipBackend::readBoxNode(XML::Node slotNode) const int x = boxNode.getProperty("x" , 0); const int y = boxNode.getProperty("y" , 0); - mBoxesPositions.emplace_back(x, y); + mBoxPositions.emplace_back(x, y); - std::string backgroundFile = - boxNode.getProperty("background" , std::string()); - mBoxesBackgroundFile.push_back(backgroundFile); + const auto icon = boxNode.getProperty("icon", std::string()); + mBoxIcons.push_back(icon); } } @@ -227,15 +226,15 @@ bool EquipBackend::isAmmoSlot(int slotTypeId) const Position EquipBackend::getBoxPosition(unsigned int slotIndex) const { - if (slotIndex < mBoxesPositions.size()) - return mBoxesPositions.at(slotIndex); + if (slotIndex < mBoxPositions.size()) + return mBoxPositions.at(slotIndex); return Position(0, 0); } -const std::string &EquipBackend::getBoxBackground(unsigned int slotIndex) const +const std::string &EquipBackend::getBoxIcon(unsigned int slotIndex) const { - if (slotIndex < mBoxesBackgroundFile.size()) - return mBoxesBackgroundFile.at(slotIndex); + if (slotIndex < mBoxIcons.size()) + return mBoxIcons.at(slotIndex); return Net::empty; } diff --git a/src/net/manaserv/inventoryhandler.h b/src/net/manaserv/inventoryhandler.h index 02ce90df..b72922b3 100644 --- a/src/net/manaserv/inventoryhandler.h +++ b/src/net/manaserv/inventoryhandler.h @@ -27,6 +27,8 @@ #include "net/manaserv/messagehandler.h" +#include "utils/xml.h" + #include <map> #include <vector> @@ -59,7 +61,7 @@ class EquipBackend final : public Equipment::Backend, public EventListener Position getBoxPosition(unsigned int slotIndex) const; - const std::string &getBoxBackground(unsigned int slotIndex) const; + const std::string &getBoxIcon(unsigned int slotIndex) const; private: void readEquipFile() override; @@ -95,8 +97,8 @@ class EquipBackend final : public Equipment::Backend, public EventListener // slot client index, slot info std::map<unsigned int, Slot> mSlots; - std::vector<Position> mBoxesPositions; - std::vector<std::string> mBoxesBackgroundFile; + std::vector<Position> mBoxPositions; + std::vector<std::string> mBoxIcons; }; class InventoryHandler final : public MessageHandler, Net::InventoryHandler, @@ -126,8 +128,8 @@ class InventoryHandler final : public MessageHandler, Net::InventoryHandler, Position getBoxPosition(unsigned int slotIndex) const override { return mEquipBackend.getBoxPosition(slotIndex); } - const std::string& getBoxBackground(unsigned int slotIndex) const override - { return mEquipBackend.getBoxBackground(slotIndex); } + const std::string& getBoxIcon(unsigned int slotIndex) const override + { return mEquipBackend.getBoxIcon(slotIndex); } private: EquipBackend mEquipBackend; diff --git a/src/net/tmwa/inventoryhandler.h b/src/net/tmwa/inventoryhandler.h index 47226bea..f5ef4492 100644 --- a/src/net/tmwa/inventoryhandler.h +++ b/src/net/tmwa/inventoryhandler.h @@ -23,6 +23,7 @@ #include "eventlistener.h" #include "inventory.h" +#include "item.h" #include "log.h" #include "playerinfo.h" diff --git a/src/position.h b/src/position.h index 7093351d..6f471cc1 100644 --- a/src/position.h +++ b/src/position.h @@ -29,7 +29,7 @@ */ struct Position { - Position(int x, int y): + constexpr Position(int x, int y): x(x), y(y) { } diff --git a/src/resources/theme.cpp b/src/resources/theme.cpp index 489fd5b3..c76499b9 100644 --- a/src/resources/theme.cpp +++ b/src/resources/theme.cpp @@ -233,7 +233,11 @@ Theme::Theme(const std::string &path) mColors[HYPERLINK].ch = '<'; } -Theme::~Theme() = default; +Theme::~Theme() +{ + for (auto &[_, image] : mIcons) + delete image; +} std::string Theme::prepareThemePath() { @@ -359,6 +363,15 @@ const Skin &Theme::getSkin(SkinType skinType) const return it != mSkins.end() ? it->second : emptySkin; } +const Image *Theme::getIcon(const std::string &name) const +{ + auto it = mIcons.find(name); + if (it == mIcons.end()) + return nullptr; + + return it->second; +} + void Theme::setMinimumOpacity(float minimumOpacity) { if (minimumOpacity > 1.0f) @@ -420,6 +433,10 @@ bool Theme::readTheme(const std::string &filename) readColorNode(childNode); else if (childNode.name() == "progressbar") readProgressBarNode(childNode); + else if (childNode.name() == "icon") + readIconNode(childNode); + else + logger->log("Theme: Unknown node '%s'!", childNode.name().data()); } logger->log("Finished loading theme."); @@ -651,6 +668,42 @@ void Theme::readSkinStateRectNode(XML::Node node, SkinState &state) const node.attribute("fill", rect.filled); } +void Theme::readIconNode(XML::Node node) +{ + std::string name; + std::string src; + node.attribute("name", name); + node.attribute("src", src); + + if (check(!name.empty(), "Theme: 'icon' element has empty 'name' attribute!")) + return; + if (check(!src.empty(), "Theme: 'icon' element has empty 'src' attribute!")) + return; + + auto image = getImage(src); + if (check(image, "Theme: Failed to load image '%s'!", src.c_str())) + return; + + int x = 0; + int y = 0; + int width = image->getWidth(); + int height = image->getHeight(); + + node.attribute("x", x); + node.attribute("y", y); + node.attribute("width", width); + node.attribute("height", height); + + if (check(x >= 0 || y >= 0, "Theme: Invalid position value!")) + return; + if (check(width >= 0 || height >= 0, "Theme: Invalid size value!")) + return; + if (check(x + width <= image->getWidth() || y + height <= image->getHeight(), "Theme: Image size out of bounds!")) + return; + + mIcons[name] = image->getSubImage(x, y, width, height); +} + static int readColorType(const std::string &type) { static constexpr const char *colors[Theme::THEME_COLORS_END] = { diff --git a/src/resources/theme.h b/src/resources/theme.h index 5c662cd7..51f1e94f 100644 --- a/src/resources/theme.h +++ b/src/resources/theme.h @@ -257,6 +257,8 @@ class Theme : public Palette, public EventListener const Skin &getSkin(SkinType skinType) const; + const Image *getIcon(const std::string &name) const; + /** * Get the current GUI alpha value. */ @@ -289,11 +291,13 @@ class Theme : public Palette, public EventListener void readSkinStateTextNode(XML::Node node, SkinState &state) const; void readSkinStateImgNode(XML::Node node, SkinState &state) const; void readSkinStateRectNode(XML::Node node, SkinState &state) const; + void readIconNode(XML::Node node); void readColorNode(XML::Node node); void readProgressBarNode(XML::Node node); std::string mThemePath; std::map<SkinType, Skin> mSkins; + std::map<std::string, Image *> mIcons; /** * Tells if the current skins opacity |