summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThorbjørn Lindeijer <bjorn@lindeijer.nl>2025-04-09 11:39:12 +0200
committerThorbjørn Lindeijer <bjorn@lindeijer.nl>2025-04-25 14:05:30 +0000
commitcf6e213f060866ac5da46d4c516365b3cbedc7b3 (patch)
treec46f56caf305a9b166183c4f66a76e03d1d990d7
parent6536aff2d24a48a4e91d31eb35971e4b788ec3b0 (diff)
downloadmana-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.xml11
-rw-r--r--data/graphics/gui/theme.xml11
-rw-r--r--src/gui/equipmentwindow.cpp75
-rw-r--r--src/gui/equipmentwindow.h20
-rw-r--r--src/gui/itemamountwindow.cpp4
-rw-r--r--src/net/inventoryhandler.h44
-rw-r--r--src/net/manaserv/inventoryhandler.cpp17
-rw-r--r--src/net/manaserv/inventoryhandler.h12
-rw-r--r--src/net/tmwa/inventoryhandler.h1
-rw-r--r--src/position.h2
-rw-r--r--src/resources/theme.cpp55
-rw-r--r--src/resources/theme.h4
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