Переглянути джерело

Initial version of radial wheel for army management

Ivan Savenko 2 роки тому
батько
коміт
dca3785f84

BIN
Mods/vcmi/Data/radialMenu/stackInfo.png


BIN
Mods/vcmi/Data/radialMenu/stackMerge.png


BIN
Mods/vcmi/Data/radialMenu/stackMove.png


BIN
Mods/vcmi/Data/radialMenu/stackSplitDialog.png


BIN
Mods/vcmi/Data/radialMenu/stackSplitEqual.png


BIN
Mods/vcmi/Data/radialMenu/stackSplitOne.png


BIN
Mods/vcmi/Data/radialMenu/stackSwap.png


+ 137 - 22
client/widgets/CGarrisonInt.cpp

@@ -15,6 +15,7 @@
 
 #include "../gui/CGuiHandler.h"
 #include "../gui/WindowHandler.h"
+#include "../render/IImage.h"
 #include "../windows/CCreatureWindow.h"
 #include "../windows/GUIClasses.h"
 #include "../CGameInfo.h"
@@ -29,6 +30,90 @@
 #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)
@@ -227,8 +312,6 @@ bool CGarrisonSlot::highlightOrDropArtifact()
 bool CGarrisonSlot::split()
 {
 	const CGarrisonSlot * selection = owner->getSelection();
-	owner->p2 = ID;   // store the second stack pos
-	owner->pb = upg;  // store the second stack owner (up or down army)
 	owner->setSplittingMode(false);
 
 	int minLeft=0, minRight=0;
@@ -252,8 +335,12 @@ bool CGarrisonSlot::split()
 	int countLeft = selection->myStack ? selection->myStack->count : 0;
 	int countRight = myStack ? myStack->count : 0;
 
-	GH.windows().createAndPushWindow<CSplitWindow>(selection->creature, std::bind(&CGarrisonInt::splitStacks, owner, _1, _2),
-		minLeft, minRight, countLeft, countRight);
+	auto splitFunctor = [this, selection](int amountLeft, int amountRight)
+	{
+		owner->splitStacks(selection, owner->armedObjs[upg], ID, amountRight);
+	};
+
+	GH.windows().createAndPushWindow<CSplitWindow>(selection->creature,  splitFunctor, minLeft, minRight, countLeft, countRight);
 	return true;
 }
 
@@ -349,17 +436,50 @@ void CGarrisonSlot::clickPressed(const Point & cursorPosition)
 		}
 }
 
+void CGarrisonSlot::gesturePanning(const Point & initialPosition, const Point & currentPosition, const Point & lastUpdateDistance)
+{
+	assert(radialMenu);
+
+	if (radialMenu)
+		radialMenu->gesturePanning(initialPosition, currentPosition, lastUpdateDistance);
+}
+
+void CGarrisonSlot::gesture(bool on, const Point & initialPosition, const Point & finalPosition)
+{
+	if (on)
+	{
+		OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+
+		radialMenu = std::make_shared<RadialMenu>(owner, this);
+		radialMenu->center(pos.center());
+
+		auto topParent = parent;
+		while (topParent->parent)
+			topParent = topParent->parent;
+
+		// Add radial menu to current window / topmost parent for proper rendering order
+		topParent->addChild(radialMenu.get(), false);
+	}
+	else
+	{
+		radialMenu->gesture(on, initialPosition, finalPosition);
+		radialMenu.reset();
+	}
+
+	GH.windows().totalRedraw();
+}
+
 void CGarrisonSlot::update()
 {
 	if(getObj() != nullptr)
 	{
-		addUsedEvents(LCLICK | SHOW_POPUP | HOVER);
+		addUsedEvents(LCLICK | SHOW_POPUP | GESTURE | HOVER);
 		myStack = getObj()->getStackPtr(ID);
 		creature = myStack ? myStack->type : nullptr;
 	}
 	else
 	{
-		removeUsedEvents(LCLICK | SHOW_POPUP | HOVER);
+		removeUsedEvents(LCLICK | SHOW_POPUP | GESTURE | HOVER);
 		myStack = nullptr;
 		creature = nullptr;
 	}
@@ -434,9 +554,7 @@ void CGarrisonSlot::splitIntoParts(CGarrisonSlot::EGarrisonType type, int amount
 	if(empty == SlotID())
 		return;
 
-	owner->pb = type;
-	owner->p2 = empty;
-	owner->splitStacks(1, amount);
+	owner->splitStacks(this, owner->armedObjs[type], empty, amount);
 }
 
 bool CGarrisonSlot::handleSplittingShortcuts()
@@ -555,9 +673,9 @@ void CGarrisonInt::splitClick()
 	setSplittingMode(!getSplittingMode());
 	redraw();
 }
-void CGarrisonInt::splitStacks(int, int amountRight)
+void CGarrisonInt::splitStacks(const CGarrisonSlot * from, const CArmedInstance * armyDest, SlotID slotDest, int amount )
 {
-	LOCPLINT->cb->splitStack(armedObjs[getSelection()->upg], armedObjs[pb], getSelection()->ID, p2, amountRight);
+	LOCPLINT->cb->splitStack(armedObjs[from->upg], armyDest, from->ID, slotDest, amount);
 }
 
 bool CGarrisonInt::checkSelected(const CGarrisonSlot * selected, TQuantity min) const
@@ -669,17 +787,14 @@ 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)
-		: highlighted(nullptr),
-		  inSplittingMode(false),
-		  interx(inx),
-		  garOffset(garsOffset),
-		  pb(false),
-		  smallIcons(smallImgs),
-		  removableUnits(_removableUnits),
-		  layout(_layout)
+CGarrisonInt::CGarrisonInt(int x, int y, int inx, const Point & garsOffset, const CArmedInstance * s1, const CArmedInstance * s2, bool _removableUnits, bool smallImgs, ESlotsLayout _layout)
+	: highlighted(nullptr)
+	, inSplittingMode(false)
+	, interx(inx)
+	, garOffset(garsOffset)
+	, smallIcons(smallImgs)
+	, removableUnits(_removableUnits)
+	, layout(_layout)
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
 

+ 48 - 7
client/widgets/CGarrisonInt.h

@@ -24,10 +24,47 @@ class CButton;
 class CAnimImage;
 class CGarrisonSlot;
 class CLabel;
+class IImage;
+
+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;
+};
 
 /// 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
@@ -43,7 +80,9 @@ class CGarrisonSlot : public CIntObject
 	std::shared_ptr<CAnimImage> creatureImage;
 	std::shared_ptr<CAnimImage> selectionImage; // image for selection, not always visible
 	std::shared_ptr<CLabel> stackCount;
+	std::shared_ptr<RadialMenu> radialMenu;
 
+public:
 	bool viewInfo();
 	bool highlightOrDropArtifact();
 	bool split();
@@ -52,7 +91,6 @@ class CGarrisonSlot : public CIntObject
 	void setHighlight(bool on);
 	std::function<void()> getDismiss() const;
 
-public:
 	virtual void hover (bool on) override; //call-in
 	const CArmedInstance * getObj() const;
 	bool our() const;
@@ -60,6 +98,10 @@ public:
 	bool ally() const;
 	void showPopupWindow(const Point & cursorPosition) override;
 	void clickPressed(const Point & cursorPosition) override;
+
+	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);
 
@@ -92,11 +134,10 @@ public:
 	Point garOffset;  ///< Offset between garrisons (not used if only one hero)
 	std::vector<std::shared_ptr<CButton>> splitButtons;  ///< May be empty if no buttons
 
-	SlotID p2; ///< TODO: comment me
-	bool pb,
-		 smallIcons,      ///< true - 32x32 imgs, false - 58x64
-		 removableUnits,  ///< player Can remove units from up
-		 owned[2];        ///< player Owns up or down army ([0] upper, [1] lower)
+	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;
 
@@ -117,7 +158,7 @@ public:
 	void recreateSlots();
 
 	void splitClick();  ///< handles click on split button
-	void splitStacks(int amountLeft, int amountRight);  ///< TODO: comment me
+	void splitStacks(const CGarrisonSlot * from, const CArmedInstance * armyDest, SlotID slotDest, int amount);  ///< TODO: comment me
 	void moveStackToAnotherArmy(const CGarrisonSlot * selected);
 	void bulkMoveArmy(const CGarrisonSlot * selected);
 	void bulkMergeStacks(const CGarrisonSlot * selected); // Gather all creatures of selected type to the selected slot from other hero/garrison slots