瀏覽代碼

Refactoring of CGarrisonInt

Ivan Savenko 2 年之前
父節點
當前提交
a494dc4870

+ 2 - 0
client/CMakeLists.txt

@@ -107,6 +107,7 @@ set(client_SRCS
 	widgets/CArtifactsOfHeroMarket.cpp
 	widgets/CArtifactsOfHeroBackpack.cpp
 	widgets/CWindowWithArtifacts.cpp
+	widgets/RadialMenu.cpp
 
 	windows/CCastleInterface.cpp
 	windows/CCreatureWindow.cpp
@@ -262,6 +263,7 @@ set(client_HEADERS
 	widgets/CArtifactsOfHeroMarket.h
 	widgets/CArtifactsOfHeroBackpack.h
 	widgets/CWindowWithArtifacts.h
+	widgets/RadialMenu.h
 
 	windows/CCastleInterface.h
 	windows/CCreatureWindow.h

+ 4 - 3
client/CPlayerInterface.cpp

@@ -34,6 +34,7 @@
 #include "windows/CQuestLog.h"
 #include "windows/CPuzzleWindow.h"
 #include "widgets/CComponent.h"
+#include "widgets/CGarrisonInt.h"
 #include "widgets/Buttons.h"
 #include "windows/CTradeWindow.h"
 #include "windows/CSpellWindow.h"
@@ -536,8 +537,8 @@ void CPlayerInterface::heroInGarrisonChange(const CGTownInstance *town)
 	if(castleInt)
 	{
 		castleInt->garr->selectSlot(nullptr);
-		castleInt->garr->setArmy(town->getUpperArmy(), 0);
-		castleInt->garr->setArmy(town->visitingHero, 1);
+		castleInt->garr->setArmy(town->getUpperArmy(), EGarrisonType::UPPER);
+		castleInt->garr->setArmy(town->visitingHero, EGarrisonType::LOWER);
 		castleInt->garr->recreateSlots();
 		castleInt->heroes->update();
 
@@ -601,7 +602,7 @@ void CPlayerInterface::garrisonsChanged(std::vector<const CGObjectInstance *> ob
 			adventureInt->onTownChanged(town);
 	}
 
-	for (auto cgh : GH.windows().findWindows<CGarrisonHolder>())
+	for (auto cgh : GH.windows().findWindows<IGarrisonHolder>())
 		cgh->updateGarrisons();
 
 	for (auto cmw : GH.windows().findWindows<CTradeWindow>())

+ 6 - 0
client/gui/CIntObject.h

@@ -143,6 +143,12 @@ protected:
 	void close();
 };
 
+class IGarrisonHolder
+{
+public:
+	virtual void updateGarrisons() = 0;
+};
+
 class IStatusBar
 {
 public:

+ 75 - 132
client/widgets/CGarrisonInt.cpp

@@ -12,6 +12,7 @@
 
 #include "Buttons.h"
 #include "TextControls.h"
+#include "RadialMenu.h"
 
 #include "../gui/CGuiHandler.h"
 #include "../gui/WindowHandler.h"
@@ -30,90 +31,6 @@
 #include "../../lib/TextOperations.h"
 #include "../../lib/gameState/CGameState.h"
 
-RadialMenuItem::RadialMenuItem(std::string imageName, std::function<void()> callback)
-	: callback(callback)
-{
-	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
-
-	image = IImage::createFromFile("radialMenu/" + imageName);
-	picture = std::make_shared<CPicture>(image, Point(0,0));
-	pos = picture->pos;
-}
-
-bool RadialMenuItem::isInside(const Point & position)
-{
-	Point localPosition = position - pos.topLeft();
-
-	return !image->isTransparent(localPosition);
-}
-
-void RadialMenuItem::gesturePanning(const Point & initialPosition, const Point & currentPosition, const Point & lastUpdateDistance)
-{
-
-}
-
-void RadialMenuItem::gesture(bool on, const Point & initialPosition, const Point & finalPosition)
-{
-
-}
-
-RadialMenu::RadialMenu(CGarrisonInt * army, CGarrisonSlot * slot)
-{
-	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
-
-	bool isExchange = army->armedObjs[0] && army->armedObjs[1]; // two armies exist
-
-	addItem(ITEM_NW, "stackMerge", [=](){army->bulkMergeStacks(slot);});
-	addItem(ITEM_NE, "stackInfo", [=](){slot->viewInfo();});
-
-	addItem(ITEM_WW, "stackSplitOne", [=](){slot->splitIntoParts(slot->upg, 1); });
-	addItem(ITEM_EE, "stackSplitEqual", [=](){army->bulkSmartSplitStack(slot);});
-
-	if (isExchange)
-	{
-		addItem(ITEM_SW, "stackMove", [=](){army->moveStackToAnotherArmy(slot);});
-		//FIXME: addItem(ITEM_SE, "stackSplitDialog", [=](){slot->split();});
-	}
-
-	for(const auto & item : items)
-		pos = pos.include(item->pos);
-
-}
-
-void RadialMenu::addItem(const Point & offset, const std::string & path, std::function<void()> callback )
-{
-	auto item = std::make_shared<RadialMenuItem>(path, callback);
-
-	item->moveBy(offset);
-
-	items.push_back(item);
-}
-
-void RadialMenu::gesturePanning(const Point & initialPosition, const Point & currentPosition, const Point & lastUpdateDistance)
-{
-
-}
-
-void RadialMenu::show(Canvas & to)
-{
-	showAll(to);
-}
-
-void RadialMenu::gesture(bool on, const Point & initialPosition, const Point & finalPosition)
-{
-	if (!on)
-	{
-		for(const auto & item : items)
-		{
-			if (item->isInside(finalPosition))
-			{
-				item->callback();
-				return;
-			}
-		}
-	}
-}
-
 void CGarrisonSlot::setHighlight(bool on)
 {
 	if (on)
@@ -157,16 +74,16 @@ void CGarrisonSlot::hover (bool on)
 			}
 			else
 			{
-				const bool isHeroOnMap = owner->armedObjs[0] // Hero is not a visitor and not a garrison defender
-					&& owner->armedObjs[0]->ID == Obj::HERO
-					&& (!owner->armedObjs[1] || owner->armedObjs[1]->ID == Obj::HERO) // one hero or we are in the Heroes exchange window
-					&& !(static_cast<const CGHeroInstance*>(owner->armedObjs[0]))->inTownGarrison;
+				const bool isHeroOnMap = owner->upperArmy() // Hero is not a visitor and not a garrison defender
+					&& owner->upperArmy()->ID == Obj::HERO
+					&& (!owner->lowerArmy() || owner->lowerArmy()->ID == Obj::HERO) // one hero or we are in the Heroes exchange window
+					&& !(static_cast<const CGHeroInstance*>(owner->upperArmy()))->inTownGarrison;
 
 				if(isHeroOnMap)
 				{
 					temp = CGI->generaltexth->allTexts[481]; //Select %s
 				}
-				else if(upg == EGarrisonType::UP)
+				else if(upg == EGarrisonType::UPPER)
 				{
 					temp = CGI->generaltexth->tcommands[12]; //Select %s (in garrison)
 				}
@@ -210,16 +127,14 @@ void CGarrisonSlot::hover (bool on)
 
 const CArmedInstance * CGarrisonSlot::getObj() const
 {
-	return 	owner->armedObjs[upg];
+	return owner->army(upg);
 }
 
-/// @return Whether the unit in the slot belongs to the current player.
 bool CGarrisonSlot::our() const
 {
-	return owner->owned[upg];
+	return owner->isArmyOwned(upg);
 }
 
-/// @return Whether the unit in the slot belongs to an ally but not to the current player.
 bool CGarrisonSlot::ally() const
 {
 	if(!getObj())
@@ -337,19 +252,13 @@ bool CGarrisonSlot::split()
 
 	auto splitFunctor = [this, selection](int amountLeft, int amountRight)
 	{
-		owner->splitStacks(selection, owner->armedObjs[upg], ID, amountRight);
+		owner->splitStacks(selection, owner->army(upg), ID, amountRight);
 	};
 
 	GH.windows().createAndPushWindow<CSplitWindow>(selection->creature,  splitFunctor, minLeft, minRight, countLeft, countRight);
 	return true;
 }
 
-/// If certain creates cannot be moved, the selection should change
-/// Force reselection in these cases
-///     * When attempting to take creatures from ally
-///     * When attempting to swap creatures with an ally
-///     * When attempting to take unremovable units
-/// @return Whether reselection must be done
 bool CGarrisonSlot::mustForceReselection() const
 {
 	const CGarrisonSlot * selection = owner->getSelection();
@@ -364,10 +273,10 @@ bool CGarrisonSlot::mustForceReselection() const
 		return true;
 	if (!owner->removableUnits)
 	{
-		if (selection->upg == EGarrisonType::UP)
+		if (selection->upg == EGarrisonType::UPPER)
 			return true;
 		else
-			return creature || upg == EGarrisonType::UP;
+			return creature || upg == EGarrisonType::UPPER;
 	}
 	return false;
 }
@@ -405,7 +314,7 @@ void CGarrisonSlot::clickPressed(const Point & cursorPosition)
 		}
 		else
 		{
-			const CArmedInstance * selectedObj = owner->armedObjs[selection->upg];
+			const CArmedInstance * selectedObj = owner->army(selection->upg);
 			bool lastHeroStackSelected = false;
 			if(selectedObj->stacksCount() == 1
 				&& owner->getSelection()->upg != upg
@@ -420,13 +329,13 @@ void CGarrisonSlot::clickPressed(const Point & cursorPosition)
 				refr = split();
 			}
 			else if(!creature && lastHeroStackSelected) // split all except last creature
-				LOCPLINT->cb->splitStack(selectedObj, owner->armedObjs[upg], selection->ID, ID, selection->myStack->count - 1);
+				LOCPLINT->cb->splitStack(selectedObj, owner->army(upg), selection->ID, ID, selection->myStack->count - 1);
 			else if(creature != selection->creature) // swap
-				LOCPLINT->cb->swapCreatures(owner->armedObjs[upg], selectedObj, ID, selection->ID);
+				LOCPLINT->cb->swapCreatures(owner->army(upg), selectedObj, ID, selection->ID);
 			else if(lastHeroStackSelected) // merge last stack to other hero stack
 				refr = split();
 			else // merge
-				LOCPLINT->cb->mergeStacks(selectedObj, owner->armedObjs[upg], selection->ID, ID);
+				LOCPLINT->cb->mergeStacks(selectedObj, owner->army(upg), selection->ID, ID);
 		}
 		if(refr)
 		{
@@ -499,7 +408,7 @@ void CGarrisonSlot::update()
 	}
 }
 
-CGarrisonSlot::CGarrisonSlot(CGarrisonInt * Owner, int x, int y, SlotID IID, CGarrisonSlot::EGarrisonType Upg, const CStackInstance * creature_)
+CGarrisonSlot::CGarrisonSlot(CGarrisonInt * Owner, int x, int y, SlotID IID, EGarrisonType Upg, const CStackInstance * creature_)
 	: ID(IID),
 	owner(Owner),
 	myStack(creature_),
@@ -547,14 +456,14 @@ CGarrisonSlot::CGarrisonSlot(CGarrisonInt * Owner, int x, int y, SlotID IID, CGa
 	update();
 }
 
-void CGarrisonSlot::splitIntoParts(CGarrisonSlot::EGarrisonType type, int amount)
+void CGarrisonSlot::splitIntoParts(EGarrisonType type, int amount)
 {
 	auto empty = owner->getEmptySlot(type);
 
 	if(empty == SlotID())
 		return;
 
-	owner->splitStacks(this, owner->armedObjs[type], empty, amount);
+	owner->splitStacks(this, owner->army(type), empty, amount);
 }
 
 bool CGarrisonSlot::handleSplittingShortcuts()
@@ -618,21 +527,23 @@ void CGarrisonInt::addSplitBtn(std::shared_ptr<CButton> button)
 void CGarrisonInt::createSlots()
 {
 	int distance = interx + (smallIcons ? 32 : 58);
-	for(int i = 0; i < 2; i++)
+	for(auto i : { EGarrisonType::UPPER, EGarrisonType::LOWER })
 	{
+		Point offset = garOffset * static_cast<int>(i);
+
 		std::vector<std::shared_ptr<CGarrisonSlot>> garrisonSlots;
 		garrisonSlots.resize(7);
-		if(armedObjs[i])
+		if(army(i))
 		{
-			for(auto & elem : armedObjs[i]->Slots())
+			for(auto & elem : army(i)->Slots())
 			{
-				garrisonSlots[elem.first.getNum()] = std::make_shared<CGarrisonSlot>(this, i*garOffset.x + (elem.first.getNum()*distance), i*garOffset.y, elem.first, static_cast<CGarrisonSlot::EGarrisonType>(i), elem.second);
+				garrisonSlots[elem.first.getNum()] = std::make_shared<CGarrisonSlot>(this, offset.x + (elem.first.getNum()*distance), offset.y, elem.first, i, elem.second);
 			}
 		}
 		for(int j = 0; j < 7; j++)
 		{
 			if(!garrisonSlots[j])
-				garrisonSlots[j] = std::make_shared<CGarrisonSlot>(this, i*garOffset.x + (j*distance), i*garOffset.y, SlotID(j), static_cast<CGarrisonSlot::EGarrisonType>(i), nullptr);
+				garrisonSlots[j] = std::make_shared<CGarrisonSlot>(this, offset.x + (j*distance), offset.x, SlotID(j), i, nullptr);
 
 			if(layout == ESlotsLayout::TWO_ROWS && j >= 4)
 			{
@@ -673,6 +584,7 @@ void CGarrisonInt::splitClick()
 	setSplittingMode(!getSplittingMode());
 	redraw();
 }
+
 void CGarrisonInt::splitStacks(const CGarrisonSlot * from, const CArmedInstance * armyDest, SlotID slotDest, int amount )
 {
 	LOCPLINT->cb->splitStack(armedObjs[from->upg], armyDest, from->ID, slotDest, amount);
@@ -689,9 +601,9 @@ void CGarrisonInt::moveStackToAnotherArmy(const CGarrisonSlot * selected)
 		return;
 
 	const auto srcArmyType = selected->upg;
-	const auto destArmyType = srcArmyType == CGarrisonSlot::UP
-		? CGarrisonSlot::DOWN
-		: CGarrisonSlot::UP;
+	const auto destArmyType = srcArmyType == EGarrisonType::UPPER
+		? EGarrisonType::LOWER
+		: EGarrisonType::UPPER;
 
 	auto srcArmy = armedObjs[srcArmyType];
 	auto destArmy = armedObjs[destArmyType];
@@ -733,9 +645,9 @@ void CGarrisonInt::bulkMoveArmy(const CGarrisonSlot * selected)
 		return;
 
 	const auto srcArmyType = selected->upg;
-	const auto destArmyType = (srcArmyType == CGarrisonSlot::UP)
-		? CGarrisonSlot::DOWN
-		: CGarrisonSlot::UP;
+	const auto destArmyType = (srcArmyType == EGarrisonType::UPPER)
+		? EGarrisonType::LOWER
+		: EGarrisonType::UPPER;
 
 	auto srcArmy = armedObjs[srcArmyType];
 	auto destArmy = armedObjs[destArmyType];
@@ -787,7 +699,7 @@ void CGarrisonInt::bulkSmartSplitStack(const CGarrisonSlot * selected)
 	LOCPLINT->cb->bulkSmartSplitStack(armedObjs[type]->id, selected->ID);
 }
 
-CGarrisonInt::CGarrisonInt(int x, int y, int inx, const Point & garsOffset, const CArmedInstance * s1, const CArmedInstance * s2, bool _removableUnits, bool smallImgs, ESlotsLayout _layout)
+CGarrisonInt::CGarrisonInt(const Point & position, int inx, const Point & garsOffset, const CArmedInstance * s1, const CArmedInstance * s2, bool _removableUnits, bool smallImgs, ESlotsLayout _layout)
 	: highlighted(nullptr)
 	, inSplittingMode(false)
 	, interx(inx)
@@ -798,10 +710,9 @@ CGarrisonInt::CGarrisonInt(int x, int y, int inx, const Point & garsOffset, cons
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
 
-	setArmy(s1, false);
-	setArmy(s2, true);
-	pos.x += x;
-	pos.y += y;
+	setArmy(s1, EGarrisonType::UPPER);
+	setArmy(s2, EGarrisonType::LOWER);
+	pos += position;
 	createSlots();
 }
 
@@ -846,19 +757,51 @@ bool CGarrisonInt::getSplittingMode()
 	return inSplittingMode;
 }
 
-SlotID CGarrisonInt::getEmptySlot(CGarrisonSlot::EGarrisonType type) const
+SlotID CGarrisonInt::getEmptySlot(EGarrisonType type) const
 {
-	assert(armedObjs[type]);
-	return armedObjs[type] ? armedObjs[type]->getFreeSlot() : SlotID();
+	assert(army(type));
+	return army(type) ? army(type)->getFreeSlot() : SlotID();
 }
 
-bool CGarrisonInt::hasEmptySlot(CGarrisonSlot::EGarrisonType type) const
+bool CGarrisonInt::hasEmptySlot(EGarrisonType type) const
 {
 	return getEmptySlot(type) != SlotID();
 }
 
-void CGarrisonInt::setArmy(const CArmedInstance * army, bool bottomGarrison)
+const CArmedInstance * CGarrisonInt::upperArmy() const
+{
+	return army(EGarrisonType::UPPER);
+}
+
+const CArmedInstance * CGarrisonInt::lowerArmy() const
+{
+	return army(EGarrisonType::LOWER);
+}
+
+const CArmedInstance * CGarrisonInt::army(EGarrisonType which) const
+{
+	if(armedObjs.count(which))
+		return armedObjs.at(which);
+	return nullptr;
+}
+
+bool CGarrisonInt::isArmyOwned(EGarrisonType which) const
+{
+	const auto * object = army(which);
+
+	if (!object)
+		return false;
+
+	if (object->tempOwner == LOCPLINT->playerID)
+		return true;
+
+	if (object->tempOwner == PlayerColor::UNFLAGGABLE)
+		return true;
+
+	return false;
+}
+
+void CGarrisonInt::setArmy(const CArmedInstance * army, EGarrisonType type)
 {
-	owned[bottomGarrison] =  army ? (army->tempOwner == LOCPLINT->playerID || army->tempOwner == PlayerColor::UNFLAGGABLE) : false;
-	armedObjs[bottomGarrison] = army;
+	armedObjs[type] = army;
 }

+ 34 - 66
client/widgets/CGarrisonInt.h

@@ -9,12 +9,11 @@
  */
 #pragma once
 
-#include "../windows/CWindowObject.h"
+#include "../gui/CIntObject.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
 class CArmedInstance;
-class CCreatureSet;
 class CStackInstance;
 
 VCMI_LIB_NAMESPACE_END
@@ -22,60 +21,23 @@ VCMI_LIB_NAMESPACE_END
 class CGarrisonInt;
 class CButton;
 class CAnimImage;
-class CGarrisonSlot;
 class CLabel;
-class IImage;
+class RadialMenu;
 
-class RadialMenuItem : public CIntObject
+enum class EGarrisonType
 {
-	std::shared_ptr<IImage> image;
-	std::shared_ptr<CPicture> picture;
-public:
-	std::function<void()> callback;
-
-	RadialMenuItem(std::string imageName, std::function<void()> callback);
-
-	bool isInside(const Point & position);
-
-	void gesturePanning(const Point & initialPosition, const Point & currentPosition, const Point & lastUpdateDistance) override;
-	void gesture(bool on, const Point & initialPosition, const Point & finalPosition) override;
-};
-
-class RadialMenu : public CIntObject
-{
-	static constexpr Point ITEM_NW = Point( -35, -85);
-	static constexpr Point ITEM_NE = Point( +35, -85);
-	static constexpr Point ITEM_WW = Point( -85, 0);
-	static constexpr Point ITEM_EE = Point( +85, 0);
-	static constexpr Point ITEM_SW = Point( -35, +85);
-	static constexpr Point ITEM_SE = Point( +35, +85);
-
-	std::vector<std::shared_ptr<RadialMenuItem>> items;
-
-	void addItem(const Point & offset, const std::string & path, std::function<void()> callback );
-public:
-	RadialMenu(CGarrisonInt * army, CGarrisonSlot * slot);
-
-	void gesturePanning(const Point & initialPosition, const Point & currentPosition, const Point & lastUpdateDistance) override;
-	void gesture(bool on, const Point & initialPosition, const Point & finalPosition) override;
-	void show(Canvas & to) override;
+	UPPER, /// up garrison (Garrisoned)
+	LOWER, /// down garrison (Visiting)
 };
 
 /// A single garrison slot which holds one creature of a specific amount
 class CGarrisonSlot : public CIntObject
 {
-public:
 	SlotID ID; //for identification
 	CGarrisonInt *owner;
 	const CStackInstance * myStack; //nullptr if slot is empty
 	const CCreature * creature;
-
-	/// Type of Garrison for slot (up or down)
-	enum EGarrisonType
-	{
-		UP=0,  ///< 0 - up garrison (Garrisoned)
-		DOWN,  ///< 1 - down garrison (Visiting)
-	} upg; ///< Flag indicating if it is the up or down garrison
+	EGarrisonType upg;
 
 	std::shared_ptr<CAnimImage> creatureImage;
 	std::shared_ptr<CAnimImage> selectionImage; // image for selection, not always visible
@@ -86,24 +48,33 @@ public:
 	bool viewInfo();
 	bool highlightOrDropArtifact();
 	bool split();
+
+	/// If certain creates cannot be moved, the selection should change
+	/// Force reselection in these cases
+	///     * When attempting to take creatures from ally
+	///     * When attempting to swap creatures with an ally
+	///     * When attempting to take unremovable units
+	/// @return Whether reselection must be done
 	bool mustForceReselection() const;
 
 	void setHighlight(bool on);
 	std::function<void()> getDismiss() const;
 
-	virtual void hover (bool on) override; //call-in
 	const CArmedInstance * getObj() const;
 	bool our() const;
 	SlotID getSlot() const { return ID; }
+	EGarrisonType getGarrison() const { return upg; }
 	bool ally() const;
+
+	// CIntObject overrides
 	void showPopupWindow(const Point & cursorPosition) override;
 	void clickPressed(const Point & cursorPosition) override;
-
+	void hover (bool on) override; //call-in
 	void gesturePanning(const Point & initialPosition, const Point & currentPosition, const Point & lastUpdateDistance) override;
 	void gesture(bool on, const Point & initialPosition, const Point & finalPosition) override;
 
 	void update();
-	CGarrisonSlot(CGarrisonInt *Owner, int x, int y, SlotID IID, EGarrisonType Upg=EGarrisonType::UP, const CStackInstance * creature_ = nullptr);
+	CGarrisonSlot(CGarrisonInt *Owner, int x, int y, SlotID IID, EGarrisonType Upg, const CStackInstance * creature_);
 
 	void splitIntoParts(EGarrisonType type, int amount);
 	bool handleSplittingShortcuts(); /// Returns true when some shortcut is pressed, false otherwise
@@ -122,6 +93,8 @@ class CGarrisonInt :public CIntObject
 	void createSlots();
 	bool checkSelected(const CGarrisonSlot * selected, TQuantity min = 0) const;
 
+	std::map<EGarrisonType, const CArmedInstance*> armedObjs;
+
 public:
 	enum class ESlotsLayout
 	{
@@ -137,7 +110,6 @@ public:
 	bool smallIcons;      ///< true - 32x32 imgs, false - 58x64
 	bool removableUnits;  ///< player Can remove units from up
 	bool twoRows;         ///< slots Will be placed in 2 rows
-	bool owned[2];        ///< player Owns up or down army ([0] upper, [1] lower)
 
 	ESlotsLayout layout;
 
@@ -147,16 +119,19 @@ public:
 	void setSplittingMode(bool on);
 	bool getSplittingMode();
 
-	bool hasEmptySlot(CGarrisonSlot::EGarrisonType type) const;
-	SlotID getEmptySlot(CGarrisonSlot::EGarrisonType type) const;
+	bool hasEmptySlot(EGarrisonType type) const;
+	SlotID getEmptySlot(EGarrisonType type) const;
 
-	const CArmedInstance * armedObjs[2];  ///< [0] is upper, [1] is down
-
-	void setArmy(const CArmedInstance * army, bool bottomGarrison);
+	void setArmy(const CArmedInstance * army, EGarrisonType type);
 	void addSplitBtn(std::shared_ptr<CButton> button);
 
 	void recreateSlots();
 
+	const CArmedInstance* upperArmy() const;
+	const CArmedInstance* lowerArmy() const;
+	const CArmedInstance* army(EGarrisonType which) const;
+	bool isArmyOwned(EGarrisonType which) const;
+
 	void splitClick();  ///< handles click on split button
 	void splitStacks(const CGarrisonSlot * from, const CArmedInstance * armyDest, SlotID slotDest, int amount);  ///< TODO: comment me
 	void moveStackToAnotherArmy(const CGarrisonSlot * selected);
@@ -166,24 +141,17 @@ public:
 	void bulkSmartSplitStack(const CGarrisonSlot * selected);
 
 	/// Constructor
-	/// @param x, y Position
-	/// @param inx Distance between slots;
-	/// @param garsOffset
+	/// @param position Relative position to parent element
+	/// @param slotInterval Distance between slots;
+	/// @param secondGarrisonOffset
 	/// @param s1, s2 Top and bottom armies
 	/// @param _removableUnits You can take units from top
 	/// @param smallImgs Units images size 64x58 or 32x32
 	/// @param _layout - when TWO_ROWS - Display slots in 2 rows (1st row = 4 slots, 2nd = 3 slots), REVERSED_TWO_ROWS = 3 slots in 1st row
-	CGarrisonInt(int x, int y, int inx,
-				 const Point & garsOffset,
-				 const CArmedInstance * s1, const CArmedInstance * s2 = nullptr,
+	CGarrisonInt(const Point & position, int slotInterval,
+				 const Point & secondGarrisonOffset,
+				 const CArmedInstance * upperArmy, const CArmedInstance * lowerArmy = nullptr,
 				 bool _removableUnits = true,
 				 bool smallImgs = false,
 				 ESlotsLayout _layout = ESlotsLayout::ONE_ROW);
 };
-
-class CGarrisonHolder
-{
-public:
-	virtual void updateGarrisons() = 0;
-};
-

+ 2 - 2
client/widgets/MiscWidgets.cpp

@@ -304,7 +304,7 @@ CHeroTooltip::CHeroTooltip(Point pos, const CGHeroInstance * hero):
 }
 
 CInteractableHeroTooltip::CInteractableHeroTooltip(Point pos, const CGHeroInstance * hero):
-		CGarrisonInt(pos.x, pos.y+73, 4, Point(0, 0), hero, nullptr, true, true, CGarrisonInt::ESlotsLayout::REVERSED_TWO_ROWS)
+		CGarrisonInt(pos + Point(0, 73), 4, Point(0, 0), hero, nullptr, true, true, CGarrisonInt::ESlotsLayout::REVERSED_TWO_ROWS)
 {
 	init(InfoAboutHero(hero, InfoAboutHero::EInfoLevel::DETAILED));
 }
@@ -383,7 +383,7 @@ CTownTooltip::CTownTooltip(Point pos, const CGTownInstance * town)
 }
 
 CInteractableTownTooltip::CInteractableTownTooltip(Point pos, const CGTownInstance * town)
-		: CGarrisonInt(pos.x, pos.y+73, 4, Point(0, 0), town->getUpperArmy(), nullptr, true, true, CGarrisonInt::ESlotsLayout::REVERSED_TWO_ROWS)
+		: CGarrisonInt(pos + Point(0, 73), 4, Point(0, 0), town->getUpperArmy(), nullptr, true, true, CGarrisonInt::ESlotsLayout::REVERSED_TWO_ROWS)
 {
 	init(InfoAboutTown(town, true));
 }

+ 101 - 0
client/widgets/RadialMenu.cpp

@@ -0,0 +1,101 @@
+/*
+ * RadialMenu.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#include "StdInc.h"
+#include "RadialMenu.h"
+
+#include "Images.h"
+
+#include "../gui/CGuiHandler.h"
+#include "../render/IImage.h"
+#include "CGarrisonInt.h"
+
+RadialMenuItem::RadialMenuItem(std::string imageName, std::function<void()> callback)
+	: callback(callback)
+{
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+
+	image = IImage::createFromFile("radialMenu/" + imageName);
+	picture = std::make_shared<CPicture>(image, Point(0,0));
+	pos = picture->pos;
+}
+
+bool RadialMenuItem::isInside(const Point & position)
+{
+	Point localPosition = position - pos.topLeft();
+
+	return !image->isTransparent(localPosition);
+}
+
+void RadialMenuItem::gesturePanning(const Point & initialPosition, const Point & currentPosition, const Point & lastUpdateDistance)
+{
+
+}
+
+void RadialMenuItem::gesture(bool on, const Point & initialPosition, const Point & finalPosition)
+{
+
+}
+
+RadialMenu::RadialMenu(CGarrisonInt * army, CGarrisonSlot * slot)
+{
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+
+	bool isExchange = army->upperArmy() && army->lowerArmy(); // two armies exist
+
+	addItem(ITEM_NW, "stackMerge", [=](){army->bulkMergeStacks(slot);});
+	addItem(ITEM_NE, "stackInfo", [=](){slot->viewInfo();});
+
+	addItem(ITEM_WW, "stackSplitOne", [=](){slot->splitIntoParts(slot->getGarrison(), 1); });
+	addItem(ITEM_EE, "stackSplitEqual", [=](){army->bulkSmartSplitStack(slot);});
+
+	if (isExchange)
+	{
+		addItem(ITEM_SW, "stackMove", [=](){army->moveStackToAnotherArmy(slot);});
+		//FIXME: addItem(ITEM_SE, "stackSplitDialog", [=](){slot->split();});
+	}
+
+	for(const auto & item : items)
+		pos = pos.include(item->pos);
+
+}
+
+void RadialMenu::addItem(const Point & offset, const std::string & path, std::function<void()> callback )
+{
+	auto item = std::make_shared<RadialMenuItem>(path, callback);
+
+	item->moveBy(offset);
+
+	items.push_back(item);
+}
+
+void RadialMenu::gesturePanning(const Point & initialPosition, const Point & currentPosition, const Point & lastUpdateDistance)
+{
+
+}
+
+void RadialMenu::show(Canvas & to)
+{
+	showAll(to);
+}
+
+void RadialMenu::gesture(bool on, const Point & initialPosition, const Point & finalPosition)
+{
+	if (!on)
+	{
+		for(const auto & item : items)
+		{
+			if (item->isInside(finalPosition))
+			{
+				item->callback();
+				return;
+			}
+		}
+	}
+}

+ 52 - 0
client/widgets/RadialMenu.h

@@ -0,0 +1,52 @@
+/*
+ * RadialMenu.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "../gui/CIntObject.h"
+
+class IImage;
+
+class CGarrisonInt;
+class CGarrisonSlot;
+
+class RadialMenuItem : public CIntObject
+{
+	std::shared_ptr<IImage> image;
+	std::shared_ptr<CPicture> picture;
+public:
+	std::function<void()> callback;
+
+	RadialMenuItem(std::string imageName, std::function<void()> callback);
+
+	bool isInside(const Point & position);
+
+	void gesturePanning(const Point & initialPosition, const Point & currentPosition, const Point & lastUpdateDistance) override;
+	void gesture(bool on, const Point & initialPosition, const Point & finalPosition) override;
+};
+
+class RadialMenu : public CIntObject
+{
+	static constexpr Point ITEM_NW = Point( -35, -85);
+	static constexpr Point ITEM_NE = Point( +35, -85);
+	static constexpr Point ITEM_WW = Point( -85, 0);
+	static constexpr Point ITEM_EE = Point( +85, 0);
+	static constexpr Point ITEM_SW = Point( -35, +85);
+	static constexpr Point ITEM_SE = Point( +35, +85);
+
+	std::vector<std::shared_ptr<RadialMenuItem>> items;
+
+	void addItem(const Point & offset, const std::string & path, std::function<void()> callback );
+public:
+	RadialMenu(CGarrisonInt * army, CGarrisonSlot * slot);
+
+	void gesturePanning(const Point & initialPosition, const Point & currentPosition, const Point & lastUpdateDistance) override;
+	void gesture(bool on, const Point & initialPosition, const Point & finalPosition) override;
+	void show(Canvas & to) override;
+};

+ 2 - 1
client/windows/CCastleInterface.cpp

@@ -25,6 +25,7 @@
 #include "../gui/WindowHandler.h"
 #include "../widgets/MiscWidgets.h"
 #include "../widgets/CComponent.h"
+#include "../widgets/CGarrisonInt.h"
 #include "../widgets/Buttons.h"
 #include "../widgets/TextControls.h"
 #include "../render/Canvas.h"
@@ -1154,7 +1155,7 @@ CCastleInterface::CCastleInterface(const CGTownInstance * Town, const CGTownInst
 	center();
 	updateShadow();
 
-	garr = std::make_shared<CGarrisonInt>(305, 387, 4, Point(0,96), town->getUpperArmy(), town->visitingHero);
+	garr = std::make_shared<CGarrisonInt>(Point(305, 387), 4, Point(0,96), town->getUpperArmy(), town->visitingHero);
 	garr->setRedrawParent(true);
 
 	heroes = std::make_shared<HeroSlots>(town, Point(241, 387), Point(241, 483), garr, true);

+ 2 - 2
client/windows/CCastleInterface.h

@@ -9,7 +9,7 @@
  */
 #pragma once
 
-#include "../widgets/CGarrisonInt.h"
+#include "../windows/CWindowObject.h"
 #include "../widgets/Images.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
@@ -212,7 +212,7 @@ public:
 };
 
 /// Class which manages the castle window
-class CCastleInterface : public CStatusbarWindow, public CGarrisonHolder
+class CCastleInterface : public CStatusbarWindow, public IGarrisonHolder
 {
 	std::shared_ptr<CLabel> title;
 	std::shared_ptr<CLabel> income;

+ 2 - 1
client/windows/CHeroWindow.cpp

@@ -24,6 +24,7 @@
 #include "../gui/WindowHandler.h"
 #include "../widgets/MiscWidgets.h"
 #include "../widgets/CComponent.h"
+#include "../widgets/CGarrisonInt.h"
 #include "../widgets/TextControls.h"
 #include "../widgets/Buttons.h"
 #include "../render/CAnimation.h"
@@ -200,7 +201,7 @@ void CHeroWindow::update(const CGHeroInstance * hero, bool redrawNeeded)
 			std::string helpBox = heroscrn[32];
 			boost::algorithm::replace_first(helpBox, "%s", CGI->generaltexth->allTexts[43]);
 
-			garr = std::make_shared<CGarrisonInt>(15, 485, 8, Point(), curHero);
+			garr = std::make_shared<CGarrisonInt>(Point(15, 485), 8, Point(), curHero);
 			auto split = std::make_shared<CButton>(Point(539, 519), "hsbtns9.def", CButton::tooltip(CGI->generaltexth->allTexts[256], helpBox), [&](){ garr->splitClick(); });
 			garr->addSplitBtn(split);
 		}

+ 6 - 5
client/windows/CHeroWindow.h

@@ -9,12 +9,12 @@
  */
 #pragma once
 
-#include <vcmi/FactionMember.h>
+#include "../widgets/CWindowWithArtifacts.h"
+#include "CWindowObject.h"
 
-#include "../../lib/bonuses/Bonus.h"
 #include "../../lib/bonuses/IBonusBearer.h"
-#include "../widgets/CWindowWithArtifacts.h"
-#include "../widgets/CGarrisonInt.h"
+
+#include <vcmi/FactionMember.h>
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -33,6 +33,7 @@ class CToggleButton;
 class CToggleGroup;
 class CGStatusBar;
 class CTextBox;
+class CGarrisonInt;
 
 /// Button which switches hero selection
 class CHeroSwitcher : public CIntObject
@@ -46,7 +47,7 @@ public:
 	CHeroSwitcher(CHeroWindow * owner_, Point pos_, const CGHeroInstance * hero_);
 };
 
-class CHeroWindow : public CStatusbarWindow, public CGarrisonHolder, public CWindowWithArtifacts
+class CHeroWindow : public CStatusbarWindow, public IGarrisonHolder, public CWindowWithArtifacts
 {
 	std::shared_ptr<CLabel> name;
 	std::shared_ptr<CLabel> title;

+ 8 - 7
client/windows/CKingdomInterface.cpp

@@ -20,6 +20,7 @@
 #include "../gui/CGuiHandler.h"
 #include "../gui/Shortcut.h"
 #include "../widgets/CComponent.h"
+#include "../widgets/CGarrisonInt.h"
 #include "../widgets/TextControls.h"
 #include "../widgets/MiscWidgets.h"
 #include "../widgets/Buttons.h"
@@ -646,7 +647,7 @@ void CKingdomInterface::heroRemoved()
 
 void CKingdomInterface::updateGarrisons()
 {
-	if(auto garrison = std::dynamic_pointer_cast<CGarrisonHolder>(tabArea->getItem()))
+	if(auto garrison = std::dynamic_pointer_cast<IGarrisonHolder>(tabArea->getItem()))
 		garrison->updateGarrisons();
 }
 
@@ -692,7 +693,7 @@ void CKingdHeroList::updateGarrisons()
 {
 	for(std::shared_ptr<CIntObject> object : heroes->getItems())
 	{
-		if(CGarrisonHolder * garrison = dynamic_cast<CGarrisonHolder*>(object.get()))
+		if(IGarrisonHolder * garrison = dynamic_cast<IGarrisonHolder*>(object.get()))
 			garrison->updateGarrisons();
 	}
 }
@@ -744,7 +745,7 @@ void CKingdTownList::updateGarrisons()
 {
 	for(std::shared_ptr<CIntObject> object : towns->getItems())
 	{
-		if(CGarrisonHolder * garrison = dynamic_cast<CGarrisonHolder*>(object.get()))
+		if(IGarrisonHolder * garrison = dynamic_cast<IGarrisonHolder*>(object.get()))
 			garrison->updateGarrisons();
 	}
 }
@@ -772,7 +773,7 @@ CTownItem::CTownItem(const CGTownInstance * Town)
 	hall = std::make_shared<CTownInfo>( 69, 31, town, true);
 	fort = std::make_shared<CTownInfo>(111, 31, town, false);
 
-	garr = std::make_shared<CGarrisonInt>(313, 3, 4, Point(232,0), town->getUpperArmy(), town->visitingHero, true, true, CGarrisonInt::ESlotsLayout::TWO_ROWS);
+	garr = std::make_shared<CGarrisonInt>(Point(313, 3), 4, Point(232,0), town->getUpperArmy(), town->visitingHero, true, true, CGarrisonInt::ESlotsLayout::TWO_ROWS);
 	heroes = std::make_shared<HeroSlots>(town, Point(244,6), Point(475,6), garr, false);
 
 	size_t iconIndex = town->town->clientInfo.icons[town->hasFort()][town->builded >= CGI->settings()->getInteger(EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP)];
@@ -790,8 +791,8 @@ CTownItem::CTownItem(const CGTownInstance * Town)
 void CTownItem::updateGarrisons()
 {
 	garr->selectSlot(nullptr);
-	garr->setArmy(town->getUpperArmy(), 0);
-	garr->setArmy(town->visitingHero, 1);
+	garr->setArmy(town->getUpperArmy(), EGarrisonType::UPPER);
+	garr->setArmy(town->visitingHero, EGarrisonType::LOWER);
 	garr->recreateSlots();
 }
 
@@ -913,7 +914,7 @@ CHeroItem::CHeroItem(const CGHeroInstance * Hero)
 	artButtons->addCallback(std::bind(&CHeroItem::onArtChange, this, _1));
 	artButtons->setSelected(0);
 
-	garr = std::make_shared<CGarrisonInt>(6, 78, 4, Point(), hero, nullptr, true, true);
+	garr = std::make_shared<CGarrisonInt>(Point(6, 78), 4, Point(), hero, nullptr, true, true);
 
 	portrait = std::make_shared<CAnimImage>("PortraitsLarge", hero->portrait, 0, 5, 6);
 	heroArea = std::make_shared<CHeroArea>(5, 6, hero);

+ 7 - 6
client/windows/CKingdomInterface.h

@@ -10,7 +10,7 @@
 #pragma once
 
 #include "../widgets/CWindowWithArtifacts.h"
-#include "../widgets/CGarrisonInt.h"
+#include "CWindowObject.h"
 
 class CButton;
 class CAnimImage;
@@ -27,6 +27,7 @@ class MoraleLuckBox;
 class CListBox;
 class CTabbedInt;
 class CGStatusBar;
+class CGarrisonInt;
 
 class CKingdHeroList;
 class CKingdTownList;
@@ -195,7 +196,7 @@ public:
 };
 
 /// Class which holds all parts of kingdom overview window
-class CKingdomInterface : public CWindowObject, public CGarrisonHolder, public CArtifactHolder
+class CKingdomInterface : public CWindowObject, public IGarrisonHolder, public CArtifactHolder
 {
 private:
 	struct OwnedObjectInfo
@@ -255,7 +256,7 @@ public:
 };
 
 /// List item with town
-class CTownItem : public CIntObject, public CGarrisonHolder
+class CTownItem : public CIntObject, public IGarrisonHolder
 {
 	std::shared_ptr<CAnimImage> background;
 	std::shared_ptr<CAnimImage> picture;
@@ -282,7 +283,7 @@ public:
 };
 
 /// List item with hero
-class CHeroItem : public CIntObject, public CGarrisonHolder
+class CHeroItem : public CIntObject, public IGarrisonHolder
 {
 	const CGHeroInstance * hero;
 
@@ -315,7 +316,7 @@ public:
 };
 
 /// Tab with all hero-specific data
-class CKingdHeroList : public CIntObject, public CGarrisonHolder, public CWindowWithArtifacts
+class CKingdHeroList : public CIntObject, public IGarrisonHolder, public CWindowWithArtifacts
 {
 private:
 	std::shared_ptr<CListBox> heroes;
@@ -331,7 +332,7 @@ public:
 };
 
 /// Tab with all town-specific data
-class CKingdTownList : public CIntObject, public CGarrisonHolder
+class CKingdTownList : public CIntObject, public IGarrisonHolder
 {
 private:
 	std::shared_ptr<CListBox> towns;

+ 4 - 3
client/windows/GUIClasses.cpp

@@ -31,6 +31,7 @@
 #include "../gui/WindowHandler.h"
 
 #include "../widgets/CComponent.h"
+#include "../widgets/CGarrisonInt.h"
 #include "../widgets/MiscWidgets.h"
 #include "../widgets/CreatureCostBox.h"
 #include "../widgets/Buttons.h"
@@ -968,7 +969,7 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2,
 
 	//garrison interface
 
-	garr = std::make_shared<CGarrisonInt>(69, qeLayout ? 122 : 131, 4, Point(418,0), heroInst[0], heroInst[1], true, true);
+	garr = std::make_shared<CGarrisonInt>(Point(69, qeLayout ? 122 : 131), 4, Point(418,0), heroInst[0], heroInst[1], true, true);
 	auto splitButtonCallback = [&](){ garr->splitClick(); };
 	garr->addSplitBtn(std::make_shared<CButton>( Point( 10, qeLayout ? 122 : 132), "TSBTNS.DEF", CButton::tooltip(CGI->generaltexth->tcommands[3]), splitButtonCallback));
 	garr->addSplitBtn(std::make_shared<CButton>( Point(744, qeLayout ? 122 : 132), "TSBTNS.DEF", CButton::tooltip(CGI->generaltexth->tcommands[3]), splitButtonCallback));
@@ -1351,7 +1352,7 @@ CGarrisonWindow::CGarrisonWindow(const CArmedInstance * up, const CGHeroInstance
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
 
-	garr = std::make_shared<CGarrisonInt>(92, 127, 4, Point(0,96), up, down, removableUnits);
+	garr = std::make_shared<CGarrisonInt>(Point(92, 127), 4, Point(0,96), up, down, removableUnits);
 	{
 		auto split = std::make_shared<CButton>(Point(88, 314), "IDV6432.DEF", CButton::tooltip(CGI->generaltexth->tcommands[3], ""), [&](){ garr->splitClick(); } );
 		garr->addSplitBtn(split);
@@ -1424,7 +1425,7 @@ CHillFortWindow::CHillFortWindow(const CGHeroInstance * visitor, const CGObjectI
 	quit = std::make_shared<CButton>(Point(294, 275), "IOKAY.DEF", CButton::tooltip(), std::bind(&CHillFortWindow::close, this), EShortcut::GLOBAL_ACCEPT);
 	statusbar = CGStatusBar::create(std::make_shared<CPicture>(background->getSurface(), Rect(8, pos.h - 26, pos.w - 16, 19), 8, pos.h - 26));
 
-	garr = std::make_shared<CGarrisonInt>(108, 60, 18, Point(), hero, nullptr);
+	garr = std::make_shared<CGarrisonInt>(Point(108, 60), 18, Point(), hero, nullptr);
 	updateGarrisons();
 }
 

+ 6 - 5
client/windows/GUIClasses.h

@@ -14,7 +14,6 @@
 #include "../lib/ResourceSet.h"
 #include "../lib/int3.h"
 #include "../widgets/CWindowWithArtifacts.h"
-#include "../widgets/CGarrisonInt.h"
 #include "../widgets/Images.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
@@ -38,6 +37,8 @@ class CToggleButton;
 class CGStatusBar;
 class CTextBox;
 class CResDataBar;
+class CGarrisonInt;
+class CGarrisonSlot;
 
 enum class EUserEvent;
 
@@ -273,7 +274,7 @@ private:
 	void moveStack(const CGHeroInstance * source, const CGHeroInstance * target, SlotID sourceSlot);
 };
 
-class CExchangeWindow : public CStatusbarWindow, public CGarrisonHolder, public CWindowWithArtifacts
+class CExchangeWindow : public CStatusbarWindow, public IGarrisonHolder, public CWindowWithArtifacts
 {
 	std::array<std::shared_ptr<CLabel>, 2> titles;
 	std::vector<std::shared_ptr<CAnimImage>> primSkillImages;//shared for both heroes
@@ -348,7 +349,7 @@ public:
 };
 
 /// Creature transformer window
-class CTransformerWindow : public CStatusbarWindow, public CGarrisonHolder
+class CTransformerWindow : public CStatusbarWindow, public IGarrisonHolder
 {
 	class CItem : public CIntObject
 	{
@@ -449,7 +450,7 @@ public:
 };
 
 /// Garrison window where you can take creatures out of the hero to place it on the garrison
-class CGarrisonWindow : public CWindowObject, public CGarrisonHolder
+class CGarrisonWindow : public CWindowObject, public IGarrisonHolder
 {
 	std::shared_ptr<CLabel> title;
 	std::shared_ptr<CAnimImage> banner;
@@ -466,7 +467,7 @@ public:
 };
 
 /// Hill fort is the building where you can upgrade units
-class CHillFortWindow : public CStatusbarWindow, public CGarrisonHolder
+class CHillFortWindow : public CStatusbarWindow, public IGarrisonHolder
 {
 private:
 	static const int slotsCount = 7;

+ 1 - 1
client/windows/QuickRecruitmentWindow.h

@@ -9,7 +9,7 @@
  */
 #pragma once
 
-#include "../widgets/CGarrisonInt.h"
+#include "../windows/CWindowObject.h"
 
 class CButton;
 class CreatureCostBox;