Pārlūkot izejas kodu

Refactoring of button class to prepare for configurable button

Ivan Savenko 1 gadu atpakaļ
vecāks
revīzija
07d201502e

+ 1 - 1
client/NetPacksLobbyClient.cpp

@@ -102,7 +102,7 @@ void ApplyOnLobbyScreenNetPackVisitor::visitLobbyChatMessage(LobbyChatMessage &
 		lobby->card->chat->addNewMessage(pack.playerName + ": " + pack.message);
 		lobby->card->setChat(true);
 		if(lobby->buttonChat)
-			lobby->buttonChat->addTextOverlay(CGI->generaltexth->allTexts[531], FONT_SMALL, Colors::WHITE);
+			lobby->buttonChat->setTextOverlay(CGI->generaltexth->allTexts[531], FONT_SMALL, Colors::WHITE);
 	}
 }
 

+ 0 - 1
client/battle/BattleInterfaceClasses.h

@@ -35,7 +35,6 @@ class BattleInterface;
 class CPicture;
 class CFilledTexture;
 class CButton;
-class CToggleButton;
 class CLabel;
 class CMultiLineLabel;
 class CTextBox;

+ 2 - 3
client/battle/BattleWindow.cpp

@@ -563,9 +563,8 @@ void BattleWindow::showAlternativeActionIcon(PossiblePlayerBattleAction action)
 			iconName = AnimationPath::fromJson(variables["actionIconNoReturn"]);
 			break;
 	}
-		
-	auto anim = GH.renderHandler().loadAnimation(iconName);
-	w->setImage(anim);
+
+	w->setImage(iconName);
 	w->redraw();
 }
 

+ 2 - 2
client/globalLobby/GlobalLobbyLoginWindow.cpp

@@ -47,8 +47,8 @@ GlobalLobbyLoginWindow::GlobalLobbyLoginWindow()
 
 	auto buttonRegister = std::make_shared<CToggleButton>(Point(10, 40),  AnimationPath::builtin("GSPBUT2"), CButton::tooltip(), 0);
 	auto buttonLogin = std::make_shared<CToggleButton>(Point(146, 40), AnimationPath::builtin("GSPBUT2"), CButton::tooltip(), 0);
-	buttonRegister->addTextOverlay(CGI->generaltexth->translate("vcmi.lobby.login.create"), EFonts::FONT_SMALL, Colors::YELLOW);
-	buttonLogin->addTextOverlay(CGI->generaltexth->translate("vcmi.lobby.login.login"), EFonts::FONT_SMALL, Colors::YELLOW);
+	buttonRegister->setTextOverlay(CGI->generaltexth->translate("vcmi.lobby.login.create"), EFonts::FONT_SMALL, Colors::YELLOW);
+	buttonLogin->setTextOverlay(CGI->generaltexth->translate("vcmi.lobby.login.login"), EFonts::FONT_SMALL, Colors::YELLOW);
 
 	toggleMode = std::make_shared<CToggleGroup>(nullptr);
 	toggleMode->addToggle(0, buttonRegister);

+ 4 - 4
client/globalLobby/GlobalLobbyServerSetup.cpp

@@ -50,8 +50,8 @@ GlobalLobbyServerSetup::GlobalLobbyServerSetup()
 
 	auto buttonPublic  = std::make_shared<CToggleButton>(Point(10, 120),  AnimationPath::builtin("GSPBUT2"), CButton::tooltip(), 0);
 	auto buttonPrivate = std::make_shared<CToggleButton>(Point(146, 120), AnimationPath::builtin("GSPBUT2"), CButton::tooltip(), 0);
-	buttonPublic->addTextOverlay(CGI->generaltexth->translate("vcmi.lobby.room.public"), EFonts::FONT_SMALL, Colors::YELLOW);
-	buttonPrivate->addTextOverlay(CGI->generaltexth->translate("vcmi.lobby.room.private"), EFonts::FONT_SMALL, Colors::YELLOW);
+	buttonPublic->setTextOverlay(CGI->generaltexth->translate("vcmi.lobby.room.public"), EFonts::FONT_SMALL, Colors::YELLOW);
+	buttonPrivate->setTextOverlay(CGI->generaltexth->translate("vcmi.lobby.room.private"), EFonts::FONT_SMALL, Colors::YELLOW);
 
 	toggleRoomType = std::make_shared<CToggleGroup>(nullptr);
 	toggleRoomType->addToggle(0, buttonPublic);
@@ -61,8 +61,8 @@ GlobalLobbyServerSetup::GlobalLobbyServerSetup()
 
 	auto buttonNewGame = std::make_shared<CToggleButton>(Point(10, 170),  AnimationPath::builtin("GSPBUT2"), CButton::tooltip(), 0);
 	auto buttonLoadGame = std::make_shared<CToggleButton>(Point(146, 170), AnimationPath::builtin("GSPBUT2"), CButton::tooltip(), 0);
-	buttonNewGame->addTextOverlay(CGI->generaltexth->translate("vcmi.lobby.room.new"), EFonts::FONT_SMALL, Colors::YELLOW);
-	buttonLoadGame->addTextOverlay(CGI->generaltexth->translate("vcmi.lobby.room.load"), EFonts::FONT_SMALL, Colors::YELLOW);
+	buttonNewGame->setTextOverlay(CGI->generaltexth->translate("vcmi.lobby.room.new"), EFonts::FONT_SMALL, Colors::YELLOW);
+	buttonLoadGame->setTextOverlay(CGI->generaltexth->translate("vcmi.lobby.room.load"), EFonts::FONT_SMALL, Colors::YELLOW);
 
 	toggleGameMode = std::make_shared<CToggleGroup>(nullptr);
 	toggleGameMode->addToggle(0, buttonNewGame);

+ 3 - 3
client/gui/InterfaceObjectConfigurable.cpp

@@ -396,7 +396,7 @@ std::shared_ptr<CToggleButton> InterfaceObjectConfigurable::buildToggleButton(co
 	{
 		for(const auto & item : config["items"].Vector())
 		{
-			button->addOverlay(buildWidget(item));
+			button->setOverlay(buildWidget(item));
 		}
 	}
 	if(!config["selected"].isNull())
@@ -422,7 +422,7 @@ std::shared_ptr<CButton> InterfaceObjectConfigurable::buildButton(const JsonNode
 	{
 		for(const auto & item : config["items"].Vector())
 		{
-			button->addOverlay(buildWidget(item));
+			button->setOverlay(buildWidget(item));
 		}
 	}
 	if(!config["imageOrder"].isNull())
@@ -589,7 +589,7 @@ std::shared_ptr<ComboBox> InterfaceObjectConfigurable::buildComboBox(const JsonN
 	{
 		for(const auto & item : config["items"].Vector())
 		{
-			result->addOverlay(buildWidget(item));
+			result->setOverlay(buildWidget(item));
 		}
 	}
 	if(!config["imageOrder"].isNull())

+ 2 - 2
client/lobby/CBonusSelection.cpp

@@ -126,7 +126,7 @@ CBonusSelection::CBonusSelection()
 		tabExtraOptions->recreate(true);
 		tabExtraOptions->setEnabled(false);
 		buttonExtraOptions = std::make_shared<CButton>(Point(643, 431), AnimationPath::builtin("GSPBUT2.DEF"), CGI->generaltexth->zelp[46], [this]{ tabExtraOptions->setEnabled(!tabExtraOptions->isActive()); GH.windows().totalRedraw(); }, EShortcut::NONE);
-		buttonExtraOptions->addTextOverlay(CGI->generaltexth->translate("vcmi.optionsTab.extraOptions.hover"), FONT_SMALL, Colors::WHITE);
+		buttonExtraOptions->setTextOverlay(CGI->generaltexth->translate("vcmi.optionsTab.extraOptions.hover"), FONT_SMALL, Colors::WHITE);
 	}
 }
 
@@ -306,7 +306,7 @@ void CBonusSelection::createBonusesIcons()
 
 		auto anim = GH.renderHandler().createAnimation();
 		anim->setCustom(picName, 0);
-		bonusButton->setImage(anim);
+//TODO:		bonusButton->setImage(anim);
 		if(CSH->campaignBonus == i)
 			bonusButton->setBorderColor(Colors::BRIGHT_YELLOW);
 		groupBonuses->addToggle(i, bonusButton);

+ 8 - 8
client/lobby/CLobbyScreen.cpp

@@ -61,7 +61,7 @@ CLobbyScreen::CLobbyScreen(ESelectionScreen screenType)
 	};
 
 	buttonChat = std::make_shared<CButton>(Point(619, 80), AnimationPath::builtin("GSPBUT2.DEF"), CGI->generaltexth->zelp[48], std::bind(&CLobbyScreen::toggleChat, this), EShortcut::LOBBY_HIDE_CHAT);
-	buttonChat->addTextOverlay(CGI->generaltexth->allTexts[532], FONT_SMALL, Colors::WHITE);
+	buttonChat->setTextOverlay(CGI->generaltexth->allTexts[532], FONT_SMALL, Colors::WHITE);
 
 	switch(screenType)
 	{
@@ -171,18 +171,18 @@ void CLobbyScreen::toggleMode(bool host)
 		return;
 
 	auto buttonColor = host ? Colors::WHITE : Colors::ORANGE;
-	buttonSelect->addTextOverlay(CGI->generaltexth->allTexts[500], FONT_SMALL, buttonColor);
-	buttonOptions->addTextOverlay(CGI->generaltexth->allTexts[501], FONT_SMALL, buttonColor);
+	buttonSelect->setTextOverlay(CGI->generaltexth->allTexts[500], FONT_SMALL, buttonColor);
+	buttonOptions->setTextOverlay(CGI->generaltexth->allTexts[501], FONT_SMALL, buttonColor);
 
 	if (buttonTurnOptions)
-		buttonTurnOptions->addTextOverlay(CGI->generaltexth->translate("vcmi.optionsTab.turnOptions.hover"), FONT_SMALL, buttonColor);
+		buttonTurnOptions->setTextOverlay(CGI->generaltexth->translate("vcmi.optionsTab.turnOptions.hover"), FONT_SMALL, buttonColor);
 
 	if (buttonExtraOptions)
-		buttonExtraOptions->addTextOverlay(CGI->generaltexth->translate("vcmi.optionsTab.extraOptions.hover"), FONT_SMALL, buttonColor);
+		buttonExtraOptions->setTextOverlay(CGI->generaltexth->translate("vcmi.optionsTab.extraOptions.hover"), FONT_SMALL, buttonColor);
 
 	if(buttonRMG)
 	{
-		buttonRMG->addTextOverlay(CGI->generaltexth->allTexts[740], FONT_SMALL, buttonColor);
+		buttonRMG->setTextOverlay(CGI->generaltexth->allTexts[740], FONT_SMALL, buttonColor);
 		buttonRMG->block(!host);
 	}
 	buttonSelect->block(!host);
@@ -206,9 +206,9 @@ void CLobbyScreen::toggleChat()
 {
 	card->toggleChat();
 	if(card->showChat)
-		buttonChat->addTextOverlay(CGI->generaltexth->allTexts[531], FONT_SMALL, Colors::WHITE);
+		buttonChat->setTextOverlay(CGI->generaltexth->allTexts[531], FONT_SMALL, Colors::WHITE);
 	else
-		buttonChat->addTextOverlay(CGI->generaltexth->allTexts[532], FONT_SMALL, Colors::WHITE);
+		buttonChat->setTextOverlay(CGI->generaltexth->allTexts[532], FONT_SMALL, Colors::WHITE);
 }
 
 void CLobbyScreen::updateAfterStateChange()

+ 1 - 1
client/lobby/OptionsTab.cpp

@@ -917,7 +917,7 @@ OptionsTab::PlayerOptionsEntry::PlayerOptionsEntry(const PlayerSettings & S, con
 			CGI->generaltexth->zelp[180],
 			std::bind(&OptionsTab::onSetPlayerClicked, &parentTab, *s)
 		);
-		flag->hoverable = true;
+		flag->setHoverable(true);
 		flag->block(CSH->isGuest());
 	}
 	else

+ 6 - 6
client/lobby/RandomMapTab.cpp

@@ -364,9 +364,9 @@ void RandomMapTab::setMapGenOptions(std::shared_ptr<CMapGenOptions> opts)
 	if(auto w = widget<CButton>("templateButton"))
 	{
 		if(tmpl)
-			w->addTextOverlay(tmpl->getName(), EFonts::FONT_SMALL, Colors::WHITE);
+			w->setTextOverlay(tmpl->getName(), EFonts::FONT_SMALL, Colors::WHITE);
 		else
-			w->addTextOverlay(readText(variables["randomTemplate"]), EFonts::FONT_SMALL, Colors::WHITE);
+			w->setTextOverlay(readText(variables["randomTemplate"]), EFonts::FONT_SMALL, Colors::WHITE);
 	}
 	for(auto r : VLC->roadTypeHandler->objects)
 	{
@@ -388,9 +388,9 @@ void RandomMapTab::setTemplate(const CRmgTemplate * tmpl)
 	if(auto w = widget<CButton>("templateButton"))
 	{
 		if(tmpl)
-			w->addTextOverlay(tmpl->getName(), EFonts::FONT_SMALL, Colors::WHITE);
+			w->setTextOverlay(tmpl->getName(), EFonts::FONT_SMALL, Colors::WHITE);
 		else
-			w->addTextOverlay(readText(variables["randomTemplate"]), EFonts::FONT_SMALL, Colors::WHITE);
+			w->setTextOverlay(readText(variables["randomTemplate"]), EFonts::FONT_SMALL, Colors::WHITE);
 	}
 	updateMapInfoByHost();
 }
@@ -532,11 +532,11 @@ TeamAlignmentsWidget::TeamAlignmentsWidget(RandomMapTab & randomMapTab):
 				assert(button);
 				if(sel == teamId)
 				{
-					button->addOverlay(buildWidget(variables["flagsAnimation"]));
+					button->setOverlay(buildWidget(variables["flagsAnimation"]));
 				}
 				else
 				{
-					button->addOverlay(nullptr);
+					button->setOverlay(nullptr);
 				}
 				button->addCallback([this](bool)
 				{

+ 1 - 1
client/lobby/SelectionTab.cpp

@@ -217,7 +217,7 @@ SelectionTab::SelectionTab(ESelectionScreen Type)
 	if(enableUiEnhancements)
 	{
 		buttonsSortBy.push_back(std::make_shared<CButton>(Point(371, 85), AnimationPath::builtin("lobby/selectionTabSortDate"), CButton::tooltip("", CGI->generaltexth->translate("vcmi.lobby.sortDate")), std::bind(&SelectionTab::sortBy, this, ESortBy::_changeDate)));
-		buttonsSortBy.back()->setAnimateLonelyFrame(true);
+//TODO:		buttonsSortBy.back()->setAnimateLonelyFrame(true);
 	}
 
 	iconsMapFormats = GH.renderHandler().loadAnimation(AnimationPath::builtin("SCSELC.DEF"));

+ 1 - 1
client/mainmenu/CCampaignScreen.cpp

@@ -66,7 +66,7 @@ CCampaignScreen::CCampaignScreen(const JsonNode & config, std::string name)
 	if(!config[name]["exitbutton"].isNull())
 	{
 		buttonBack = createExitButton(config[name]["exitbutton"]);
-		buttonBack->hoverable = true;
+		buttonBack->setHoverable(true);
 	}
 
 	for(const JsonNode & node : config[name]["items"].Vector())

+ 1 - 1
client/mainmenu/CMainMenu.cpp

@@ -260,7 +260,7 @@ CMenuEntry::CMenuEntry(CMenuScreen * parent, const JsonNode & config)
 	for(const JsonNode & node : config["buttons"].Vector())
 	{
 		buttons.push_back(createButton(parent, node));
-		buttons.back()->hoverable = true;
+		buttons.back()->setHoverable(true);
 		buttons.back()->setRedrawParent(true);
 	}
 }

+ 97 - 91
client/widgets/Buttons.cpp

@@ -30,30 +30,25 @@
 #include "../../lib/CConfigHandler.h"
 #include "../../lib/CGeneralTextHandler.h"
 
-void CButton::update()
+void ButtonBase::update()
 {
 	if (overlay)
 	{
 		Point targetPos = Rect::createCentered( pos, overlay->pos.dimensions()).topLeft();
 
-		if (state == PRESSED)
+		if (state == EButtonState::PRESSED)
 			overlay->moveTo(targetPos + Point(1,1));
 		else
 			overlay->moveTo(targetPos);
 	}
 
 	int newPos = stateToIndex[int(state)];
-	if(animateLonelyFrame)
-	{
-		if(state == PRESSED)
-			image->moveBy(Point(1,1));
-		else
-			image->moveBy(Point(-1,-1));
-	}
 	if (newPos < 0)
 		newPos = 0;
 
-	if (state == HIGHLIGHTED && image->size() < 4)
+	// checkbox - has only have two frames: normal and pressed/highlighted
+	// hero movement speed buttons: only three frames: normal, pressed and blocked/highlighted
+	if (state == EButtonState::HIGHLIGHTED && image->size() < 4)
 		newPos = (int)image->size()-1;
 	image->setFrame(newPos);
 
@@ -71,14 +66,14 @@ void CButton::addCallback(const std::function<void()> & callback)
 	this->callback += callback;
 }
 
-void CButton::addTextOverlay(const std::string & Text, EFonts font, ColorRGBA color)
+void ButtonBase::setTextOverlay(const std::string & Text, EFonts font, ColorRGBA color)
 {
 	OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE);
-	addOverlay(std::make_shared<CLabel>(pos.w/2, pos.h/2, font, ETextAlignment::CENTER, color, Text));
+	setOverlay(std::make_shared<CLabel>(pos.w/2, pos.h/2, font, ETextAlignment::CENTER, color, Text));
 	update();
 }
 
-void CButton::addOverlay(std::shared_ptr<CIntObject> newOverlay)
+void ButtonBase::setOverlay(std::shared_ptr<CIntObject> newOverlay)
 {
 	overlay = newOverlay;
 	if(overlay)
@@ -90,17 +85,23 @@ void CButton::addOverlay(std::shared_ptr<CIntObject> newOverlay)
 	update();
 }
 
-void CButton::addImage(const AnimationPath & filename)
+void ButtonBase::setImage(const AnimationPath & defName, bool playerColoredButton)
 {
-	imageNames.push_back(filename);
+	OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE);
+
+	image = std::make_shared<CAnimImage>(defName, vstd::to_underlying(getState()));
+	pos = image->pos;
+
+	if (playerColoredButton)
+		image->playerColored(LOCPLINT->playerID);
 }
 
-void CButton::addHoverText(ButtonState state, std::string text)
+void CButton::addHoverText(EButtonState state, std::string text)
 {
-	hoverTexts[state] = text;
+	hoverTexts[vstd::to_underlying(state)] = text;
 }
 
-void CButton::setImageOrder(int state1, int state2, int state3, int state4)
+void ButtonBase::setImageOrder(int state1, int state2, int state3, int state4)
 {
 	stateToIndex[0] = state1;
 	stateToIndex[1] = state2;
@@ -109,44 +110,65 @@ void CButton::setImageOrder(int state1, int state2, int state3, int state4)
 	update();
 }
 
-void CButton::setAnimateLonelyFrame(bool agreement)
+//TODO:
+//void CButton::setAnimateLonelyFrame(bool agreement)
+//{
+//	animateLonelyFrame = agreement;
+//}
+
+void ButtonBase::setStateImpl(EButtonState newState)
 {
-	animateLonelyFrame = agreement;
+	state = newState;
+	update();
 }
-void CButton::setState(ButtonState newState)
+
+void CButton::setState(EButtonState newState)
 {
-	if (state == newState)
+	if (getState() == newState)
 		return;
 
-	if (newState == BLOCKED)
+	if (newState == EButtonState::BLOCKED)
 		removeUsedEvents(LCLICK | SHOW_POPUP | HOVER | KEYBOARD);
 	else
 		addUsedEvents(LCLICK | SHOW_POPUP | HOVER | KEYBOARD);
 
-
-	state = newState;
-	update();
+	setStateImpl(newState);
 }
 
-CButton::ButtonState CButton::getState()
+EButtonState ButtonBase::getState()
 {
 	return state;
 }
 
 bool CButton::isBlocked()
 {
-	return state == BLOCKED;
+	return getState() == EButtonState::BLOCKED;
 }
 
 bool CButton::isHighlighted()
 {
-	return state == HIGHLIGHTED;
+	return getState() == EButtonState::HIGHLIGHTED;
+}
+
+void CButton::setHoverable(bool on)
+{
+	hoverable = on;
+}
+
+void CButton::setSoundDisabled(bool on)
+{
+	soundDisabled = on;
+}
+
+void CButton::setActOnDown(bool on)
+{
+	actOnDown = on;
 }
 
 void CButton::block(bool on)
 {
-	if(on || state == BLOCKED) //dont change button state if unblock requested, but it's not blocked
-		setState(on ? BLOCKED : NORMAL);
+	if(on || getState() == EButtonState::BLOCKED) //dont change button state if unblock requested, but it's not blocked
+		setState(on ? EButtonState::BLOCKED : EButtonState::NORMAL);
 }
 
 void CButton::onButtonClicked()
@@ -169,14 +191,14 @@ void CButton::clickPressed(const Point & cursorPosition)
 	if(isBlocked())
 		return;
 
-	if (getState() != PRESSED)
+	if (getState() != EButtonState::PRESSED)
 	{
 		if (!soundDisabled)
 		{
 			CCS->soundh->playSound(soundBase::button);
 			GH.input().hapticFeedback();
 		}
-		setState(PRESSED);
+		setState(EButtonState::PRESSED);
 
 		if (actOnDown)
 			onButtonClicked();
@@ -185,12 +207,12 @@ void CButton::clickPressed(const Point & cursorPosition)
 
 void CButton::clickReleased(const Point & cursorPosition)
 {
-	if (getState() == PRESSED)
+	if (getState() == EButtonState::PRESSED)
 	{
 		if(hoverable && isHovered())
-			setState(HIGHLIGHTED);
+			setState(EButtonState::HIGHLIGHTED);
 		else
-			setState(NORMAL);
+			setState(EButtonState::NORMAL);
 
 		if (!actOnDown)
 			onButtonClicked();
@@ -199,12 +221,12 @@ void CButton::clickReleased(const Point & cursorPosition)
 
 void CButton::clickCancel(const Point & cursorPosition)
 {
-	if (getState() == PRESSED)
+	if (getState() == EButtonState::PRESSED)
 	{
 		if(hoverable && isHovered())
-			setState(HIGHLIGHTED);
+			setState(EButtonState::HIGHLIGHTED);
 		else
-			setState(NORMAL);
+			setState(EButtonState::NORMAL);
 	}
 }
 
@@ -219,17 +241,17 @@ void CButton::hover (bool on)
 	if(hoverable && !isBlocked())
 	{
 		if(on)
-			setState(HIGHLIGHTED);
+			setState(EButtonState::HIGHLIGHTED);
 		else
-			setState(NORMAL);
+			setState(EButtonState::NORMAL);
 	}
 
 	/*if(pressedL && on) // WTF is this? When this is used?
 		setState(PRESSED);*/
 
-	std::string name = hoverTexts[getState()].empty()
+	std::string name = hoverTexts[vstd::to_underlying(getState())].empty()
 		? hoverTexts[0]
-		: hoverTexts[getState()];
+		: hoverTexts[vstd::to_underlying(getState())];
 
 	if(!name.empty() && !isBlocked()) //if there is no name, there is nothing to display also
 	{
@@ -240,55 +262,30 @@ void CButton::hover (bool on)
 	}
 }
 
+ButtonBase::ButtonBase(Point position, const AnimationPath & defName, EShortcut key, bool playerColoredButton)
+	: CKeyShortcut(key)
+	, stateToIndex({0, 1, 2, 3})
+	, state(EButtonState::NORMAL)
+{
+	pos.x += position.x;
+	pos.y += position.y;
+
+	setImage(defName);
+}
+
 CButton::CButton(Point position, const AnimationPath &defName, const std::pair<std::string, std::string> &help, CFunctionList<void()> Callback, EShortcut key, bool playerColoredButton):
-    CKeyShortcut(key),
+	ButtonBase(position, defName, key, playerColoredButton),
     callback(Callback)
 {
 	defActions = 255-DISPOSE;
 	addUsedEvents(LCLICK | SHOW_POPUP | HOVER | KEYBOARD);
 
-	stateToIndex[0] = 0;
-	stateToIndex[1] = 1;
-	stateToIndex[2] = 2;
-	stateToIndex[3] = 3;
-
-	state=NORMAL;
-
-	currentImage = -1;
 	hoverable = actOnDown = soundDisabled = false;
 	hoverTexts[0] = help.first;
 	helpBox=help.second;
-
-	pos.x += position.x;
-	pos.y += position.y;
-
-	if (!defName.empty())
-	{
-		imageNames.push_back(defName);
-		setIndex(0);
-		if (playerColoredButton)
-			image->playerColored(LOCPLINT->playerID);
-	}
-}
-
-void CButton::setIndex(size_t index)
-{
-	if (index == currentImage || index>=imageNames.size())
-		return;
-	currentImage = index;
-	auto anim = GH.renderHandler().loadAnimation(imageNames[index]);
-	setImage(anim);
-}
-
-void CButton::setImage(std::shared_ptr<CAnimation> anim, int animFlags)
-{
-	OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE);
-
-	image = std::make_shared<CAnimImage>(anim, getState(), 0, 0, 0, animFlags);
-	pos = image->pos;
 }
 
-void CButton::setPlayerColor(PlayerColor player)
+void ButtonBase::setPlayerColor(PlayerColor player)
 {
 	if (image && image->isPlayerColored())
 		image->playerColored(player);
@@ -353,6 +350,11 @@ void CToggleBase::setSelected(bool on)
 		callback(on);
 }
 
+bool CToggleBase::isSelected() const
+{
+	return selected;
+}
+
 bool CToggleBase::canActivate()
 {
 	if (selected && !allowDeselection)
@@ -365,29 +367,33 @@ void CToggleBase::addCallback(std::function<void(bool)> function)
 	callback += function;
 }
 
+void CToggleBase::setAllowDeselection(bool on)
+{
+	allowDeselection = on;
+}
+
 CToggleButton::CToggleButton(Point position, const AnimationPath &defName, const std::pair<std::string, std::string> &help,
 							 CFunctionList<void(bool)> callback, EShortcut key, bool playerColoredButton):
   CButton(position, defName, help, 0, key, playerColoredButton),
   CToggleBase(callback)
 {
-	allowDeselection = true;
 }
 
 void CToggleButton::doSelect(bool on)
 {
 	if (on)
 	{
-		setState(HIGHLIGHTED);
+		setState(EButtonState::HIGHLIGHTED);
 	}
 	else
 	{
-		setState(NORMAL);
+		setState(EButtonState::NORMAL);
 	}
 }
 
 void CToggleButton::setEnabled(bool enabled)
 {
-	setState(enabled ? NORMAL : BLOCKED);
+	setState(enabled ? EButtonState::NORMAL : EButtonState::BLOCKED);
 }
 
 void CToggleButton::clickPressed(const Point & cursorPosition)
@@ -403,7 +409,7 @@ void CToggleButton::clickPressed(const Point & cursorPosition)
 	{
 		CCS->soundh->playSound(soundBase::button);
 		GH.input().hapticFeedback();
-		setState(PRESSED);
+		setState(EButtonState::PRESSED);
 	}
 }
 
@@ -416,13 +422,13 @@ void CToggleButton::clickReleased(const Point & cursorPosition)
 	if(isBlocked())
 		return;
 
-	if (getState() == PRESSED && canActivate())
+	if (getState() == EButtonState::PRESSED && canActivate())
 	{
 		onButtonClicked();
-		setSelected(!selected);
+		setSelected(!isSelected());
 	}
 	else
-		doSelect(selected); // restore
+		doSelect(isSelected()); // restore
 }
 
 void CToggleButton::clickCancel(const Point & cursorPosition)
@@ -434,7 +440,7 @@ void CToggleButton::clickCancel(const Point & cursorPosition)
 	if(isBlocked())
 		return;
 
-	doSelect(selected);
+	doSelect(isSelected());
 }
 
 void CToggleGroup::addCallback(std::function<void(int)> callback)
@@ -455,7 +461,7 @@ void CToggleGroup::addToggle(int identifier, std::shared_ptr<CToggleBase> button
 	}
 
 	button->addCallback([=] (bool on) { if (on) selectionChanged(identifier);});
-	button->allowDeselection = false;
+	button->setAllowDeselection(false);
 
 	if(buttons.count(identifier)>0)
 		logAnim->error("Duplicated toggle button id %d", identifier);

+ 55 - 43
client/widgets/Buttons.h

@@ -22,64 +22,77 @@ class CAnimImage;
 class CLabel;
 class CAnimation;
 
-/// Typical Heroes 3 button which can be inactive or active and can
-/// hold further information if you right-click it
-class CButton : public CKeyShortcut
+enum class EButtonState
 {
-	CFunctionList<void()> callback;
+	NORMAL=0,
+	PRESSED=1,
+	BLOCKED=2,
+	HIGHLIGHTED=3 // used for: highlighted state for selectable buttons, hovered state for hoverable buttons (e.g. main menu)
+};
+
+class ButtonBase : public CKeyShortcut
+{
+	std::shared_ptr<CAnimImage> image; //image for this button
+	std::shared_ptr<CIntObject> overlay;//object-overlay, can be null
+
+	std::array<int, 4> stateToIndex; // mapping of button state to index of frame in animation
+
+	EButtonState state;//current state of button from enum
+
+	void update();//to refresh button after image or text change
 
-public:
-	enum ButtonState
-	{
-		NORMAL=0,
-		PRESSED=1,
-		BLOCKED=2,
-		HIGHLIGHTED=3
-	};
 protected:
-	std::vector<AnimationPath> imageNames;//store list of images that can be used by this button
-	size_t currentImage;
+	ButtonBase(Point position, const AnimationPath & defName, EShortcut key, bool playerColoredButton);
 
-	ButtonState state;//current state of button from enum
+	void setStateImpl(EButtonState state);
+	EButtonState getState();
+
+public:
+	/// Appearance modifiers
+	void setPlayerColor(PlayerColor player);
+	void setImage(const AnimationPath & defName, bool playerColoredButton = false);
+	void setImageOrder(int state1, int state2, int state3, int state4);
+
+	/// adds overlay on top of button image. Only one overlay can be active at once
+	void setOverlay(std::shared_ptr<CIntObject> newOverlay);
+	void setTextOverlay(const std::string & Text, EFonts font, ColorRGBA color);
+};
+
+/// Typical Heroes 3 button which can be inactive or active and can
+/// hold further information if you right-click it
+class CButton : public ButtonBase
+{
+	CFunctionList<void()> callback;
 
-	std::array<int, 4> stateToIndex; // mapping of button state to index of frame in animation
 	std::array<std::string, 4> hoverTexts; //texts for statusbar, if empty - first entry will be used
 	std::optional<ColorRGBA> borderColor; // mapping of button state to border color
 	std::string helpBox; //for right-click help
 
-	std::shared_ptr<CAnimImage> image; //image for this button
-	std::shared_ptr<CIntObject> overlay;//object-overlay, can be null
-	bool animateLonelyFrame = false;
+	bool actOnDown; //runs when mouse is pressed down over it, not when up
+	bool hoverable; //if true, button will be highlighted when hovered (e.g. main menu)
+	bool soundDisabled;
+
 protected:
 	void onButtonClicked(); // calls callback
-	void update();//to refresh button after image or text change
 
 	// internal method to change state. Public change can be done only via block()
-	void setState(ButtonState newState);
-	ButtonState getState();
+	void setState(EButtonState newState);
 
 public:
-	bool actOnDown,//runs when mouse is pressed down over it, not when up
-		hoverable,//if true, button will be highlighted when hovered (e.g. main menu)
-		soundDisabled;
-
 	// sets the same border color for all button states.
 	void setBorderColor(std::optional<ColorRGBA> borderColor);
 
 	/// adds one more callback to on-click actions
 	void addCallback(const std::function<void()> & callback);
 
-	/// adds overlay on top of button image. Only one overlay can be active at once
-	void addOverlay(std::shared_ptr<CIntObject> newOverlay);
-	void addTextOverlay(const std::string & Text, EFonts font, ColorRGBA color);
-
-	void addImage(const AnimationPath & filename);
-	void addHoverText(ButtonState state, std::string text);
+	void addHoverText(EButtonState state, std::string text);
 
-	void setImageOrder(int state1, int state2, int state3, int state4);
-	void setAnimateLonelyFrame(bool agreement);
 	void block(bool on);
 
+	void setHoverable(bool on);
+	void setSoundDisabled(bool on);
+	void setActOnDown(bool on);
+
 	/// State modifiers
 	bool isBlocked();
 	bool isHighlighted();
@@ -88,11 +101,6 @@ public:
 	CButton(Point position, const AnimationPath & defName, const std::pair<std::string, std::string> & help,
 			CFunctionList<void()> Callback = 0, EShortcut key = {}, bool playerColoredButton = false );
 
-	/// Appearance modifiers
-	void setIndex(size_t index);
-	void setImage(std::shared_ptr<CAnimation> anim, int animFlags=0);
-	void setPlayerColor(PlayerColor player);
-
 	/// CIntObject overrides
 	void showPopupWindow(const Point & cursorPosition) override;
 	void clickPressed(const Point & cursorPosition) override;
@@ -110,10 +118,13 @@ public:
 class CToggleBase
 {
 	CFunctionList<void(bool)> callback;
-protected:
 
 	bool selected;
 
+	/// if set to false - button can not be deselected normally
+	bool allowDeselection;
+
+protected:
 	// internal method for overrides
 	virtual void doSelect(bool on);
 
@@ -121,9 +132,6 @@ protected:
 	bool canActivate();
 
 public:
-	/// if set to false - button can not be deselected normally
-	bool allowDeselection;
-
 	CToggleBase(CFunctionList<void(bool)> callback);
 	virtual ~CToggleBase();
 
@@ -133,6 +141,10 @@ public:
 	/// Changes selection to "on" without calling callback
 	void setSelectedSilent(bool on);
 
+	bool isSelected() const;
+
+	void setAllowDeselection(bool on);
+
 	void addCallback(std::function<void(bool)> callback);
 
 	/// Set whether the toggle is currently enabled for user to use, this is only inplemented in ToggleButton, not for other toggles yet.

+ 4 - 3
client/widgets/ComboBox.cpp

@@ -168,10 +168,11 @@ ComboBox::ComboBox(Point position, const AnimationPath & defName, const std::pai
 
 void ComboBox::setItem(const void * item)
 {
-	auto w = std::dynamic_pointer_cast<CLabel>(overlay);
+	// TODO:
+	//auto w = std::dynamic_pointer_cast<CLabel>(overlay);
 
-	if( w && getItemText)
-		addTextOverlay(getItemText(0, item), w->font, w->color);
+	//if( w && getItemText)
+	//	setTextOverlay(getItemText(0, item), w->font, w->color);
 	
 	if(onSetItem)
 		onSetItem(item);

+ 4 - 4
client/widgets/Slider.cpp

@@ -206,10 +206,10 @@ CSlider::CSlider(Point position, int totalw, const std::function<void(int)> & Mo
 		right = std::make_shared<CButton>(Point(), AnimationPath::builtin(getOrientation() == Orientation::HORIZONTAL ? "SCNRBRT.DEF" : "SCNRBDN.DEF"), CButton::tooltip());
 		slider = std::make_shared<CButton>(Point(), AnimationPath::builtin("SCNRBSL.DEF"), CButton::tooltip());
 	}
-	slider->actOnDown = true;
-	slider->soundDisabled = true;
-	left->soundDisabled = true;
-	right->soundDisabled = true;
+	slider->setActOnDown(true);
+	slider->setSoundDisabled(true);
+	left->setSoundDisabled(true);
+	right->setSoundDisabled(true);
 
 	if (getOrientation() == Orientation::HORIZONTAL)
 		right->moveBy(Point(totalw - right->pos.w, 0));

+ 2 - 2
client/windows/CCastleInterface.cpp

@@ -1334,12 +1334,12 @@ void CCastleInterface::recreateIcons()
 
 	fastTownHall = std::make_shared<CButton>(Point(80, 413), AnimationPath::builtin("ITMTL.def"), CButton::tooltip(), [&](){ builds->enterTownHall(); });
 	fastTownHall->setImageOrder(town->hallLevel(), town->hallLevel(), town->hallLevel(), town->hallLevel());
-	fastTownHall->setAnimateLonelyFrame(true);
+//TODO:	fastTownHall->setAnimateLonelyFrame(true);
 
 	int imageIndex = town->fortLevel() == CGTownInstance::EFortLevel::NONE ? 3 : town->fortLevel() - 1;
 	fastArmyPurchase = std::make_shared<CButton>(Point(122, 413), AnimationPath::builtin("itmcl.def"), CButton::tooltip(), [&](){ builds->enterToTheQuickRecruitmentWindow(); });
 	fastArmyPurchase->setImageOrder(imageIndex, imageIndex, imageIndex, imageIndex);
-	fastArmyPurchase->setAnimateLonelyFrame(true);
+//TODO:	fastArmyPurchase->setAnimateLonelyFrame(true);
 
 	fastMarket = std::make_shared<LRClickableArea>(Rect(163, 410, 64, 42), [&]()
 	{

+ 2 - 2
client/windows/CCreatureWindow.cpp

@@ -340,7 +340,7 @@ CStackWindow::ButtonsSection::ButtonsSection(CStackWindow * owner, int yOffset)
 			};
 			auto upgradeBtn = std::make_shared<CButton>(Point(221 + (int)buttonIndex * 40, 5), AnimationPath::builtin("stackWindow/upgradeButton"), CGI->generaltexth->zelp[446], onClick);
 
-			upgradeBtn->addOverlay(std::make_shared<CAnimImage>(AnimationPath::builtin("CPRSMALL"), VLC->creh->objects[upgradeInfo.info.newID[buttonIndex]]->getIconIndex()));
+			upgradeBtn->setOverlay(std::make_shared<CAnimImage>(AnimationPath::builtin("CPRSMALL"), VLC->creh->objects[upgradeInfo.info.newID[buttonIndex]]->getIconIndex()));
 
 			if(buttonsToCreate == 1) // single upgrade avaialbe
 				upgradeBtn->assignedKey = EShortcut::RECRUITMENT_UPGRADE;
@@ -366,7 +366,7 @@ CStackWindow::ButtonsSection::ButtonsSection(CStackWindow * owner, int yOffset)
 
 			std::string tooltipText = "vcmi.creatureWindow." + btnIDs[buttonIndex];
 			parent->switchButtons[buttonIndex] = std::make_shared<CButton>(Point(302 + (int)buttonIndex*40, 5), AnimationPath::builtin("stackWindow/upgradeButton"), CButton::tooltipLocalized(tooltipText), onSwitch);
-			parent->switchButtons[buttonIndex]->addOverlay(std::make_shared<CAnimImage>(AnimationPath::builtin("stackWindow/switchModeIcons"), buttonIndex));
+			parent->switchButtons[buttonIndex]->setOverlay(std::make_shared<CAnimImage>(AnimationPath::builtin("stackWindow/switchModeIcons"), buttonIndex));
 		}
 		parent->switchButtons[parent->activeTab]->disable();
 	}

+ 3 - 3
client/windows/CHeroWindow.cpp

@@ -89,7 +89,7 @@ CHeroWindow::CHeroWindow(const CGHeroInstance * hero)
 	{
 		questlogButton = std::make_shared<CButton>(Point(314, 429), AnimationPath::builtin("hsbtns4.def"), CButton::tooltip(heroscrn[0]), [=](){ LOCPLINT->showQuestLog(); }, EShortcut::ADVENTURE_QUEST_LOG);
 		backpackButton = std::make_shared<CButton>(Point(424, 429), AnimationPath::builtin("buttons/backpack"), CButton::tooltipLocalized("vcmi.heroWindow.openBackpack"), [=](){ createBackpackWindow(); }, EShortcut::HERO_BACKPACK);
-		backpackButton->addOverlay(std::make_shared<CPicture>(ImagePath::builtin("buttons/backpackButtonIcon")));
+		backpackButton->setOverlay(std::make_shared<CPicture>(ImagePath::builtin("buttons/backpackButtonIcon")));
 		dismissButton = std::make_shared<CButton>(Point(534, 429), AnimationPath::builtin("hsbtns2.def"), CButton::tooltip(heroscrn[28]), [=](){ dismissCurrent(); }, EShortcut::HERO_DISMISS);
 	}
 	else
@@ -197,9 +197,9 @@ void CHeroWindow::update(const CGHeroInstance * hero, bool redrawNeeded)
 	specName->setText(curHero->type->getSpecialtyNameTranslated());
 
 	tacticsButton = std::make_shared<CToggleButton>(Point(539, 483), AnimationPath::builtin("hsbtns8.def"), std::make_pair(heroscrn[26], heroscrn[31]), 0, EShortcut::HERO_TOGGLE_TACTICS);
-	tacticsButton->addHoverText(CButton::HIGHLIGHTED, CGI->generaltexth->heroscrn[25]);
+	tacticsButton->addHoverText(EButtonState::HIGHLIGHTED, CGI->generaltexth->heroscrn[25]);
 
-	dismissButton->addHoverText(CButton::NORMAL, boost::str(boost::format(CGI->generaltexth->heroscrn[16]) % curHero->getNameTranslated() % curHero->getClassNameTranslated()));
+	dismissButton->addHoverText(EButtonState::NORMAL, boost::str(boost::format(CGI->generaltexth->heroscrn[16]) % curHero->getNameTranslated() % curHero->getClassNameTranslated()));
 	portraitArea->hoverText = boost::str(boost::format(CGI->generaltexth->allTexts[15]) % curHero->getNameTranslated() % curHero->getClassNameTranslated());
 	portraitArea->text = curHero->getBiographyTranslated();
 	portraitImage->setFrame(curHero->getIconIndex());

+ 3 - 3
client/windows/CKingdomInterface.cpp

@@ -823,11 +823,11 @@ CTownItem::CTownItem(const CGTownInstance * Town)
 
 	fastTownHall = std::make_shared<CButton>(Point(69, 31), AnimationPath::builtin("ITMTL.def"), CButton::tooltip(), [&]() { std::make_shared<CCastleBuildings>(town)->enterTownHall(); });
 	fastTownHall->setImageOrder(town->hallLevel(), town->hallLevel(), town->hallLevel(), town->hallLevel());
-	fastTownHall->setAnimateLonelyFrame(true);
+//TODO:	fastTownHall->setAnimateLonelyFrame(true);
 	int imageIndex = town->fortLevel() == CGTownInstance::EFortLevel::NONE ? 3 : town->fortLevel() - 1;
 	fastArmyPurchase = std::make_shared<CButton>(Point(111, 31), AnimationPath::builtin("itmcl.def"), CButton::tooltip(), [&]() { std::make_shared<CCastleBuildings>(town)->enterToTheQuickRecruitmentWindow(); });
 	fastArmyPurchase->setImageOrder(imageIndex, imageIndex, imageIndex, imageIndex);
-	fastArmyPurchase->setAnimateLonelyFrame(true);
+//TODO:	fastArmyPurchase->setAnimateLonelyFrame(true);
 	fastTavern = std::make_shared<LRClickableArea>(Rect(5, 6, 58, 64), [&]()
 	{
 		if(town->builtBuildings.count(BuildingID::TAVERN))
@@ -976,7 +976,7 @@ CHeroItem::CHeroItem(const CGHeroInstance * Hero)
 		std::string overlay = CGI->generaltexth->overview[8+it];
 
 		auto button = std::make_shared<CToggleButton>(Point(364+(int)it*112, 46), AnimationPath::builtin("OVBUTN3"), CButton::tooltip(hover, overlay), 0);
-		button->addTextOverlay(CGI->generaltexth->allTexts[stringID[it]], FONT_SMALL, Colors::YELLOW);
+		button->setTextOverlay(CGI->generaltexth->allTexts[stringID[it]], FONT_SMALL, Colors::YELLOW);
 		artButtons->addToggle((int)it, button);
 	}
 	artButtons->addCallback(std::bind(&CTabbedInt::setActive, artsTabs, _1));

+ 15 - 16
client/windows/GUIClasses.cpp

@@ -481,24 +481,24 @@ CTavernWindow::CTavernWindow(const CGObjectInstance * TavernObj, const std::func
 
 	if(LOCPLINT->cb->getResourceAmount(EGameResID::GOLD) < GameConstants::HERO_GOLD_COST) //not enough gold
 	{
-		recruit->addHoverText(CButton::NORMAL, CGI->generaltexth->tavernInfo[0]); //Cannot afford a Hero
+		recruit->addHoverText(EButtonState::NORMAL, CGI->generaltexth->tavernInfo[0]); //Cannot afford a Hero
 		recruit->block(true);
 	}
 	else if(LOCPLINT->cb->howManyHeroes(true) >= CGI->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_TOTAL_CAP))
 	{
 		//Cannot recruit. You already have %d Heroes.
-		recruit->addHoverText(CButton::NORMAL, boost::str(boost::format(CGI->generaltexth->tavernInfo[1]) % LOCPLINT->cb->howManyHeroes(true)));
+		recruit->addHoverText(EButtonState::NORMAL, boost::str(boost::format(CGI->generaltexth->tavernInfo[1]) % LOCPLINT->cb->howManyHeroes(true)));
 		recruit->block(true);
 	}
 	else if(LOCPLINT->cb->howManyHeroes(false) >= CGI->settings()->getInteger(EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP))
 	{
 		//Cannot recruit. You already have %d Heroes.
-		recruit->addHoverText(CButton::NORMAL, boost::str(boost::format(CGI->generaltexth->tavernInfo[1]) % LOCPLINT->cb->howManyHeroes(false)));
+		recruit->addHoverText(EButtonState::NORMAL, boost::str(boost::format(CGI->generaltexth->tavernInfo[1]) % LOCPLINT->cb->howManyHeroes(false)));
 		recruit->block(true);
 	}
 	else if(dynamic_cast<const CGTownInstance *>(TavernObj) && dynamic_cast<const CGTownInstance *>(TavernObj)->visitingHero)
 	{
-		recruit->addHoverText(CButton::NORMAL, CGI->generaltexth->tavernInfo[2]); //Cannot recruit. You already have a Hero in this town.
+		recruit->addHoverText(EButtonState::NORMAL, CGI->generaltexth->tavernInfo[2]); //Cannot recruit. You already have a Hero in this town.
 		recruit->block(true);
 	}
 	else
@@ -586,7 +586,7 @@ void CTavernWindow::show(Canvas & to)
 
 			//Recruit %s the %s
 			if (!recruit->isBlocked())
-				recruit->addHoverText(CButton::NORMAL, boost::str(boost::format(CGI->generaltexth->tavernInfo[3]) % sel->h->getNameTranslated() % sel->h->getClassNameTranslated()));
+				recruit->addHoverText(EButtonState::NORMAL, boost::str(boost::format(CGI->generaltexth->tavernInfo[3]) % sel->h->getNameTranslated() % sel->h->getClassNameTranslated()));
 
 		}
 
@@ -880,10 +880,10 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2,
 			std::bind(moveArtifacts, [this](bool equipped, bool baclpack) -> void {controller.moveArtifacts(false, equipped, baclpack);}));
 		backpackButtonLeft       = std::make_shared<CButton>(Point(325, 518), AnimationPath::builtin("buttons/backpack"), CButton::tooltipLocalized("vcmi.heroWindow.openBackpack"),
 			std::bind(openBackpack, heroInst[0]));
-		backpackButtonLeft->addOverlay(std::make_shared<CPicture>(ImagePath::builtin("buttons/backpackButtonIcon")));
+		backpackButtonLeft->setOverlay(std::make_shared<CPicture>(ImagePath::builtin("buttons/backpackButtonIcon")));
 		backpackButtonRight      = std::make_shared<CButton>(Point(419, 518), AnimationPath::builtin("buttons/backpack"), CButton::tooltipLocalized("vcmi.heroWindow.openBackpack"),
 			std::bind(openBackpack, heroInst[1]));
-		backpackButtonRight->addOverlay(std::make_shared<CPicture>(ImagePath::builtin("buttons/backpackButtonIcon")));
+		backpackButtonRight->setOverlay(std::make_shared<CPicture>(ImagePath::builtin("buttons/backpackButtonIcon")));
 
 		auto leftHeroBlock = heroInst[0]->tempOwner != LOCPLINT->cb->getPlayerID();
 		auto rightHeroBlock = heroInst[1]->tempOwner != LOCPLINT->cb->getPlayerID();
@@ -1355,9 +1355,7 @@ CHillFortWindow::CHillFortWindow(const CGHeroInstance * visitor, const CGObjectI
 
 	for(int i = 0; i < slotsCount; i++)
 	{
-		upgrade[i] = std::make_shared<CButton>(Point(107 + i * 76, 171), AnimationPath(), CButton::tooltip(getTextForSlot(SlotID(i))), [=](){ makeDeal(SlotID(i)); }, vstd::next(EShortcut::SELECT_INDEX_1, i));
-		for(auto image : { "APHLF1R.DEF", "APHLF1Y.DEF", "APHLF1G.DEF" })
-			upgrade[i]->addImage(AnimationPath::builtin(image));
+		upgrade[i] = std::make_shared<CButton>(Point(107 + i * 76, 171), AnimationPath::builtin("APHLF1R"), CButton::tooltip(getTextForSlot(SlotID(i))), [=](){ makeDeal(SlotID(i)); }, vstd::next(EShortcut::SELECT_INDEX_1, i));
 
 		for(int j : {0,1})
 		{
@@ -1366,9 +1364,7 @@ CHillFortWindow::CHillFortWindow(const CGHeroInstance * visitor, const CGObjectI
 		}
 	}
 
-	upgradeAll = std::make_shared<CButton>(Point(30, 231), AnimationPath(), CButton::tooltip(CGI->generaltexth->allTexts[432]), [&](){ makeDeal(SlotID(slotsCount));}, EShortcut::RECRUITMENT_UPGRADE_ALL);
-	for(auto image : { "APHLF4R.DEF", "APHLF4Y.DEF", "APHLF4G.DEF" })
-		upgradeAll->addImage(AnimationPath::builtin(image));
+	upgradeAll = std::make_shared<CButton>(Point(30, 231), AnimationPath::builtin("APHLF4R"), CButton::tooltip(CGI->generaltexth->allTexts[432]), [&](){ makeDeal(SlotID(slotsCount));}, EShortcut::RECRUITMENT_UPGRADE_ALL);
 
 	quit = std::make_shared<CButton>(Point(294, 275), AnimationPath::builtin("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));
@@ -1384,6 +1380,9 @@ bool CHillFortWindow::holdsGarrison(const CArmedInstance * army)
 
 void CHillFortWindow::updateGarrisons()
 {
+	constexpr std::array slotImages = { "APHLF1R.DEF", "APHLF1Y.DEF", "APHLF1G.DEF" };
+	constexpr std::array allImages =  { "APHLF4R.DEF", "APHLF4Y.DEF", "APHLF4G.DEF" };
+
 	std::array<TResources, slotsCount> costs;// costs [slot ID] [resource ID] = resource count for upgrade
 
 	TResources totalSum; // totalSum[resource ID] = value
@@ -1404,9 +1403,9 @@ void CHillFortWindow::updateGarrisons()
 		}
 
 		currState[i] = newState;
-		upgrade[i]->setIndex(currState[i] == -1 ? 0 : currState[i]);
+		upgrade[i]->setImage(AnimationPath::builtin(currState[i] == -1 ? slotImages[0] : slotImages[currState[i]]));
 		upgrade[i]->block(currState[i] == -1);
-		upgrade[i]->addHoverText(CButton::NORMAL, getTextForSlot(SlotID(i)));
+		upgrade[i]->addHoverText(EButtonState::NORMAL, getTextForSlot(SlotID(i)));
 	}
 
 	//"Upgrade all" slot
@@ -1426,7 +1425,7 @@ void CHillFortWindow::updateGarrisons()
 	}
 
 	currState[slotsCount] = newState;
-	upgradeAll->setIndex(newState);
+	upgradeAll->setImage(AnimationPath::builtin(allImages[newState]));
 
 	garr->recreateSlots();