Ver código fonte

Large refactoring of button classes:
- renamed CAdventureMapButton to more expectable CButton
- merged CButtonBase into CButton
- created more generic class for clickable elements
- created more generic class for selectable elements
- renamed CHighlightableButton to CToggleButton
- renamed CHighlightableButtonsGrous to CToggleGroup
- minimized differences between API of all these classes
- removed all but one contructors in buttons, with same parameters across all classes

Ivan Savenko 11 anos atrás
pai
commit
10fc1892a8
37 arquivos alterados com 939 adições e 931 exclusões
  1. 4 4
      client/CPlayerInterface.cpp
  2. 2 2
      client/CPlayerInterface.h
  3. 117 125
      client/CPreGame.cpp
  4. 24 24
      client/CPreGame.h
  5. 12 12
      client/battle/CBattleInterface.cpp
  6. 5 5
      client/battle/CBattleInterface.h
  7. 19 22
      client/battle/CBattleInterfaceClasses.cpp
  8. 8 8
      client/battle/CBattleInterfaceClasses.h
  9. 13 0
      client/gui/CIntObject.cpp
  10. 3 3
      client/gui/CIntObject.h
  11. 5 5
      client/widgets/AdventureMapClasses.cpp
  12. 3 3
      client/widgets/AdventureMapClasses.h
  13. 230 241
      client/widgets/Buttons.cpp
  14. 111 66
      client/widgets/Buttons.h
  15. 5 5
      client/widgets/CArtifactHolder.cpp
  16. 3 3
      client/widgets/CArtifactHolder.h
  17. 3 3
      client/widgets/CGarrisonInt.cpp
  18. 4 4
      client/widgets/CGarrisonInt.h
  19. 83 93
      client/windows/CAdvmapInterface.cpp
  20. 11 11
      client/windows/CAdvmapInterface.h
  21. 16 19
      client/windows/CCastleInterface.cpp
  22. 9 9
      client/windows/CCastleInterface.h
  23. 9 9
      client/windows/CCreatureWindow.cpp
  24. 3 3
      client/windows/CCreatureWindow.h
  25. 27 23
      client/windows/CHeroWindow.cpp
  26. 7 7
      client/windows/CHeroWindow.h
  27. 26 30
      client/windows/CKingdomInterface.cpp
  28. 8 8
      client/windows/CKingdomInterface.h
  29. 1 1
      client/windows/CQuestLog.cpp
  30. 3 3
      client/windows/CQuestLog.h
  31. 16 16
      client/windows/CTradeWindow.cpp
  32. 2 2
      client/windows/CTradeWindow.h
  33. 105 126
      client/windows/GUIClasses.cpp
  34. 24 24
      client/windows/GUIClasses.h
  35. 9 10
      client/windows/InfoWindows.cpp
  36. 2 2
      client/windows/InfoWindows.h
  37. 7 0
      config/translate.json

+ 4 - 4
client/CPlayerInterface.cpp

@@ -1292,8 +1292,8 @@ void CPlayerInterface::moveHero( const CGHeroInstance *h, CGPath path )
 	
 	if (adventureInt && adventureInt->isHeroSleeping(h))
 	{
-		adventureInt->sleepWake.clickLeft(true, false);
-		adventureInt->sleepWake.clickLeft(false, true);
+		adventureInt->sleepWake->clickLeft(true, false);
+		adventureInt->sleepWake->clickLeft(false, true);
 		//could've just called
 		//adventureInt->fsleepWake();
 		//but no authentic button click/sound ;-)
@@ -1328,7 +1328,7 @@ void CPlayerInterface::showGarrisonDialog( const CArmedInstance *up, const CGHer
 	waitForAllDialogs();
 
 	auto  cgw = new CGarrisonWindow(up,down,removableUnits);
-	cgw->quit->callback += onEnd;
+	cgw->quit->addCallback(onEnd);
 	GH.pushInt(cgw);
 }
 
@@ -2246,7 +2246,7 @@ void CPlayerInterface::acceptTurn()
 		if(CInfoWindow *iw = dynamic_cast<CInfoWindow *>(GH.topInt()))
 			iw->close();
 
-		adventureInt->endTurn.callback();
+		adventureInt->fendTurn();
 	}
 
 	// warn player if he has no town

+ 2 - 2
client/CPlayerInterface.h

@@ -30,8 +30,8 @@
  */
 
 class CDefEssential;
-class CAdventureMapButton;
-class CHighlightableButtonsGroup;
+class CButton;
+class CToggleGroup;
 class CDefHandler;
 struct TryMoveHero;
 class CDefEssential;

+ 117 - 125
client/CPreGame.cpp

@@ -372,7 +372,7 @@ static std::function<void()> genCommand(CMenuScreen* menu, std::vector<std::stri
 	return std::function<void()>();
 }
 
-CAdventureMapButton* CMenuEntry::createButton(CMenuScreen* parent, const JsonNode& button)
+CButton* CMenuEntry::createButton(CMenuScreen* parent, const JsonNode& button)
 {
 	std::function<void()> command = genCommand(parent, parent->menuNameToEntry, button["command"].String());
 
@@ -388,7 +388,7 @@ CAdventureMapButton* CMenuEntry::createButton(CMenuScreen* parent, const JsonNod
 	if (posy < 0)
 		posy = pos.h + posy;
 
-	return new CAdventureMapButton(help, command, posx, posy, button["name"].String(), button["hotkey"].Float());
+	return new CButton(Point(posx, posy), button["name"].String(), help, command, button["hotkey"].Float());
 }
 
 CMenuEntry::CMenuEntry(CMenuScreen* parent, const JsonNode &config)
@@ -644,40 +644,39 @@ CSelectionScreen::CSelectionScreen(CMenuScreen::EState Type, CMenuScreen::EMulti
 	{
 	case CMenuScreen::newGame:
 		{
-			card->difficulty->onChange = boost::bind(&CSelectionScreen::difficultyChange, this, _1);
-			card->difficulty->select(1, 0);
-			CAdventureMapButton * select = new CAdventureMapButton(CGI->generaltexth->zelp[45], 0, 411, 80, "GSPBUTT.DEF", SDLK_s);
-			select->callback = [&]()
+			SDL_Color orange = {232, 184, 32, 0};
+			SDL_Color overlayColor = multiPlayer == CMenuScreen::MULTI_NETWORK_GUEST ? orange : Colors::WHITE;
+
+			card->difficulty->addCallback(boost::bind(&CSelectionScreen::difficultyChange, this, _1));
+			card->difficulty->setSelected(1);
+			CButton * select = new CButton(Point(411, 80), "GSPBUTT.DEF", CGI->generaltexth->zelp[45], 0, SDLK_s);
+			select->addCallback([&]()
 			{
 				toggleTab(sel);
 				changeSelection(sel->getSelectedMapInfo());
-			};
-			select->addTextOverlay(CGI->generaltexth->allTexts[500], FONT_SMALL);
+			});
+			select->addTextOverlay(CGI->generaltexth->allTexts[500], FONT_SMALL, overlayColor);
 
-			CAdventureMapButton *opts = new CAdventureMapButton(CGI->generaltexth->zelp[46], boost::bind(&CSelectionScreen::toggleTab, this, opt), 411, 510, "GSPBUTT.DEF", SDLK_a);
-			opts->addTextOverlay(CGI->generaltexth->allTexts[501], FONT_SMALL);
+			CButton *opts = new CButton(Point(411, 510), "GSPBUTT.DEF", CGI->generaltexth->zelp[46], boost::bind(&CSelectionScreen::toggleTab, this, opt), SDLK_a);
+			opts->addTextOverlay(CGI->generaltexth->allTexts[501], FONT_SMALL, overlayColor);
 
-			CAdventureMapButton * randomBtn = new CAdventureMapButton(CGI->generaltexth->zelp[47], 0, 411, 105, "GSPBUTT.DEF", SDLK_r);
-			randomBtn->addTextOverlay(CGI->generaltexth->allTexts[740], FONT_SMALL);
-			randomBtn->callback = [&]()
+			CButton * randomBtn = new CButton(Point(411, 105), "GSPBUTT.DEF", CGI->generaltexth->zelp[47], 0, SDLK_r);
+			randomBtn->addTextOverlay(CGI->generaltexth->allTexts[740], FONT_SMALL, overlayColor);
+			randomBtn->addCallback([&]()
 			{
 				toggleTab(randMapTab);
 				changeSelection(randMapTab->getMapInfo());
-			};
+			});
 
-			start  = new CAdventureMapButton(CGI->generaltexth->zelp[103], boost::bind(&CSelectionScreen::startScenario, this), 411, 535, "SCNRBEG.DEF", SDLK_b);
+			start  = new CButton(Point(411, 535), "SCNRBEG.DEF", CGI->generaltexth->zelp[103], boost::bind(&CSelectionScreen::startScenario, this), SDLK_b);
 
 			if(network)
 			{
-				CAdventureMapButton *hideChat = new CAdventureMapButton(CGI->generaltexth->zelp[48], boost::bind(&InfoCard::toggleChat, card), 619, 83, "GSPBUT2.DEF", SDLK_h);
+				CButton *hideChat = new CButton(Point(619, 83), "GSPBUT2.DEF", CGI->generaltexth->zelp[48], boost::bind(&InfoCard::toggleChat, card), SDLK_h);
 				hideChat->addTextOverlay(CGI->generaltexth->allTexts[531], FONT_SMALL);
 
 				if(multiPlayer == CMenuScreen::MULTI_NETWORK_GUEST)
 				{
-					SDL_Color orange = {232, 184, 32, 0};
-					dynamic_cast<CLabel*>(select->overlay)->color    = orange;
-					dynamic_cast<CLabel*>(opts->overlay)->color      = orange;
-					dynamic_cast<CLabel*>(randomBtn->overlay)->color = orange;
 					select->block(true);
 					opts->block(true);
 					randomBtn->block(true);
@@ -688,21 +687,21 @@ CSelectionScreen::CSelectionScreen(CMenuScreen::EState Type, CMenuScreen::EMulti
 		break;
 	case CMenuScreen::loadGame:
 		sel->recActions = 255;
-		start  = new CAdventureMapButton(CGI->generaltexth->zelp[103], boost::bind(&CSelectionScreen::startScenario, this), 411, 535, "SCNRLOD.DEF", SDLK_l);
+		start  = new CButton(Point(411, 535), "SCNRLOD.DEF", CGI->generaltexth->zelp[103], boost::bind(&CSelectionScreen::startScenario, this), SDLK_l);
 		break;
 	case CMenuScreen::saveGame:
 		sel->recActions = 255;
-		start  = new CAdventureMapButton("", CGI->generaltexth->zelp[103].second, boost::bind(&CSelectionScreen::startScenario, this), 411, 535, "SCNRSAV.DEF");
+		start  = new CButton(Point(411, 535), "SCNRSAV.DEF", CGI->generaltexth->zelp[103], boost::bind(&CSelectionScreen::startScenario, this), SDLK_s);
 		break;
 	case CMenuScreen::campaignList:
 		sel->recActions = 255;
-		start  = new CAdventureMapButton(std::pair<std::string, std::string>(), boost::bind(&CSelectionScreen::startCampaign, this), 411, 535, "SCNRLOD.DEF", SDLK_b);
+		start  = new CButton(Point(411, 535), "SCNRLOD.DEF", CButton::tooltip(), boost::bind(&CSelectionScreen::startCampaign, this), SDLK_b);
 		break;
 	}
 
 	start->assignedKeys.insert(SDLK_RETURN);
 
-	back = new CAdventureMapButton("", CGI->generaltexth->zelp[105].second, boost::bind(&CGuiHandler::popIntTotally, &GH, this), 581, 535, "SCNRBACK.DEF", SDLK_ESCAPE);
+	back = new CButton(Point(581, 535), "SCNRBACK.DEF", CGI->generaltexth->zelp[105], boost::bind(&CGuiHandler::popIntTotally, &GH, this), SDLK_ESCAPE);
 
 	if(network)
 	{
@@ -999,7 +998,7 @@ void CSelectionScreen::setSInfo(const StartInfo &si)
 	if(current)
 		opt->recreate(); //will force to recreate using current sInfo
 
-	card->difficulty->select(si.difficulty, 0);
+	card->difficulty->setSelected(si.difficulty);
 
 	GH.totalRedraw();
 }
@@ -1262,7 +1261,7 @@ SelectionTab::SelectionTab(CMenuScreen::EState Type, const std::function<void(CM
 			int sizes[] = {36, 72, 108, 144, 0};
 			const char * names[] = {"SCSMBUT.DEF", "SCMDBUT.DEF", "SCLGBUT.DEF", "SCXLBUT.DEF", "SCALBUT.DEF"};
 			for(int i = 0; i < 5; i++)
-				new CAdventureMapButton("", CGI->generaltexth->zelp[54+i].second, boost::bind(&SelectionTab::filter, this, sizes[i], true), 158 + 47*i, 46, names[i]);
+				new CButton(Point(158 + 47*i, 46), names[i], CGI->generaltexth->zelp[54+i], boost::bind(&SelectionTab::filter, this, sizes[i], true));
 		}
 
 		//sort buttons buttons
@@ -1276,20 +1275,19 @@ SelectionTab::SelectionTab(CMenuScreen::EState Type, const std::function<void(CM
 				if(criteria == _name)
 					criteria = generalSortingBy;
 
-				new CAdventureMapButton("", CGI->generaltexth->zelp[107+i].second, boost::bind(&SelectionTab::sortBy, this, criteria), xpos[i], 86, names[i]);
+				new CButton(Point(xpos[i], 86), names[i], CGI->generaltexth->zelp[107+i], boost::bind(&SelectionTab::sortBy, this, criteria));
 			}
 		}
 	}
 	else
 	{
 		//sort by buttons
-		new CAdventureMapButton("", "", boost::bind(&SelectionTab::sortBy, this, _numOfMaps), 23, 86, "CamCusM.DEF"); //by num of maps
-		new CAdventureMapButton("", "", boost::bind(&SelectionTab::sortBy, this, _name), 55, 86, "CamCusL.DEF"); //by name
+		new CButton(Point(23, 86), "CamCusM.DEF", CButton::tooltip(), boost::bind(&SelectionTab::sortBy, this, _numOfMaps)); //by num of maps
+		new CButton(Point(55, 86), "CamCusL.DEF", CButton::tooltip(), boost::bind(&SelectionTab::sortBy, this, _name)); //by name
 	}
 
 	slider = new CSlider(372, 86, tabType != CMenuScreen::saveGame ? 480 : 430, boost::bind(&SelectionTab::sliderMove, this, _1), positions, curItems.size(), 0, false, 1);
 	slider->addUsedEvents(WHEEL);
-	slider->slider->keepFrame = true;
 	format =  CDefHandler::giveDef("SCSELC.DEF");
 
 	sortingBy = _format;
@@ -1575,7 +1573,7 @@ void SelectionTab::onDoubleClick()
 	if(getLine() != -1) //double clicked scenarios list
 	{
 		//act as if start button was pressed
-		(static_cast<CSelectionScreen*>(parent))->start->callback();
+		(static_cast<CSelectionScreen*>(parent))->start->clickLeft(false, true);
 	}
 }
 
@@ -1620,27 +1618,25 @@ CRandomMapTab::CRandomMapTab()
 	bg = new CPicture("RANMAPBK", 0, 6);
 
 	// Map Size
-	mapSizeBtnGroup = new CHighlightableButtonsGroup(0);
+	mapSizeBtnGroup = new CToggleGroup(0);
 	mapSizeBtnGroup->pos.y += 81;
 	mapSizeBtnGroup->pos.x += 158;
 	const std::vector<std::string> mapSizeBtns = boost::assign::list_of("RANSIZS")("RANSIZM")("RANSIZL")("RANSIZX");
 	addButtonsToGroup(mapSizeBtnGroup, mapSizeBtns, 0, 3, 47, 198);
-	mapSizeBtnGroup->select(1, false);
-	mapSizeBtnGroup->onChange = [&](int btnId)
+	mapSizeBtnGroup->setSelected(1);
+	mapSizeBtnGroup->addCallback([&](int btnId)
 	{
 		const std::vector<int> mapSizeVal = boost::assign::list_of(CMapHeader::MAP_SIZE_SMALL)(CMapHeader::MAP_SIZE_MIDDLE)
 				(CMapHeader::MAP_SIZE_LARGE)(CMapHeader::MAP_SIZE_XLARGE);
 		mapGenOptions.setWidth(mapSizeVal[btnId]);
 		mapGenOptions.setHeight(mapSizeVal[btnId]);
 		updateMapInfo();
-	};
+	});
 
 	// Two levels
-	twoLevelsBtn = new CHighlightableButton(0, 0, std::map<int,std::string>(),
-		CGI->generaltexth->zelp[202].second, false, "RANUNDR", nullptr, 346, 81);
+	twoLevelsBtn = new CToggleButton(Point(346, 81), "RANUNDR", CGI->generaltexth->zelp[202]);
 	//twoLevelsBtn->select(true); for now, deactivated
-	twoLevelsBtn->callback = [&]() { mapGenOptions.setHasTwoLevels(true); updateMapInfo(); };
-	twoLevelsBtn->callback2 = [&]() { mapGenOptions.setHasTwoLevels(false); updateMapInfo(); };
+	twoLevelsBtn->addCallback([&](bool on) { mapGenOptions.setHasTwoLevels(on); updateMapInfo(); });
 
 	// Create number defs list
 	std::vector<std::string> numberDefs;
@@ -1652,128 +1648,129 @@ CRandomMapTab::CRandomMapTab()
 	const int NUMBERS_WIDTH = 32;
 	const int BTNS_GROUP_LEFT_MARGIN = 67;
 	// Amount of players
-	playersCntGroup = new CHighlightableButtonsGroup(0);
+	playersCntGroup = new CToggleGroup(0);
 	playersCntGroup->pos.y += 153;
 	playersCntGroup->pos.x += BTNS_GROUP_LEFT_MARGIN;
 	addButtonsWithRandToGroup(playersCntGroup, numberDefs, 1, 8, NUMBERS_WIDTH, 204, 212);
-	playersCntGroup->onChange = [&](int btnId)
+	playersCntGroup->addCallback([&](int btnId)
 	{
 		mapGenOptions.setPlayerCount(btnId);
 		deactivateButtonsFrom(teamsCntGroup, btnId);
 		deactivateButtonsFrom(compOnlyPlayersCntGroup, 8 - btnId + 1);
 		validatePlayersCnt(btnId);
 		updateMapInfo();
-	};
+	});
 
 	// Amount of teams
-	teamsCntGroup = new CHighlightableButtonsGroup(0);
+	teamsCntGroup = new CToggleGroup(0);
 	teamsCntGroup->pos.y += 219;
 	teamsCntGroup->pos.x += BTNS_GROUP_LEFT_MARGIN;
 	addButtonsWithRandToGroup(teamsCntGroup, numberDefs, 0, 7, NUMBERS_WIDTH, 214, 222);
-	teamsCntGroup->onChange = [&](int btnId)
+	teamsCntGroup->addCallback([&](int btnId)
 	{
 		mapGenOptions.setTeamCount(btnId);
 		updateMapInfo();
-	};
+	});
 
 	// Computer only players
-	compOnlyPlayersCntGroup = new CHighlightableButtonsGroup(0);
+	compOnlyPlayersCntGroup = new CToggleGroup(0);
 	compOnlyPlayersCntGroup->pos.y += 285;
 	compOnlyPlayersCntGroup->pos.x += BTNS_GROUP_LEFT_MARGIN;
 	addButtonsWithRandToGroup(compOnlyPlayersCntGroup, numberDefs, 0, 7, NUMBERS_WIDTH, 224, 232);
-	compOnlyPlayersCntGroup->select(0, true);
-	compOnlyPlayersCntGroup->onChange = [&](int btnId)
+	compOnlyPlayersCntGroup->setSelected(0);
+	compOnlyPlayersCntGroup->addCallback([&](int btnId)
 	{
 		mapGenOptions.setCompOnlyPlayerCount(btnId);
 		deactivateButtonsFrom(compOnlyTeamsCntGroup, btnId);
 		validateCompOnlyPlayersCnt(btnId);
 		updateMapInfo();
-	};
+	});
 
 	// Computer only teams
-	compOnlyTeamsCntGroup = new CHighlightableButtonsGroup(0);
+	compOnlyTeamsCntGroup = new CToggleGroup(0);
 	compOnlyTeamsCntGroup->pos.y += 351;
 	compOnlyTeamsCntGroup->pos.x += BTNS_GROUP_LEFT_MARGIN;
 	addButtonsWithRandToGroup(compOnlyTeamsCntGroup, numberDefs, 0, 6, NUMBERS_WIDTH, 234, 241);
 	deactivateButtonsFrom(compOnlyTeamsCntGroup, 0);
-	compOnlyTeamsCntGroup->onChange = [&](int btnId)
+	compOnlyTeamsCntGroup->addCallback([&](int btnId)
 	{
 		mapGenOptions.setCompOnlyTeamCount(btnId);
 		updateMapInfo();
-	};
+	});
 
 	const int WIDE_BTN_WIDTH = 85;
 	// Water content
-	waterContentGroup = new CHighlightableButtonsGroup(0);
+	waterContentGroup = new CToggleGroup(0);
 	waterContentGroup->pos.y += 419;
 	waterContentGroup->pos.x += BTNS_GROUP_LEFT_MARGIN;
 	const std::vector<std::string> waterContentBtns = boost::assign::list_of("RANNONE")("RANNORM")("RANISLD");
 	addButtonsWithRandToGroup(waterContentGroup, waterContentBtns, 0, 2, WIDE_BTN_WIDTH, 243, 246);
-	waterContentGroup->onChange = [&](int btnId)
+	waterContentGroup->addCallback([&](int btnId)
 	{
 		mapGenOptions.setWaterContent(static_cast<EWaterContent::EWaterContent>(btnId));
-	};
+	});
 
 	// Monster strength
-	monsterStrengthGroup = new CHighlightableButtonsGroup(0);
+	monsterStrengthGroup = new CToggleGroup(0);
 	monsterStrengthGroup->pos.y += 485;
 	monsterStrengthGroup->pos.x += BTNS_GROUP_LEFT_MARGIN;
 	const std::vector<std::string> monsterStrengthBtns = boost::assign::list_of("RANWEAK")("RANNORM")("RANSTRG");
 	addButtonsWithRandToGroup(monsterStrengthGroup, monsterStrengthBtns, 0, 2, WIDE_BTN_WIDTH, 248, 251);
-	monsterStrengthGroup->onChange = [&](int btnId)
+	monsterStrengthGroup->addCallback([&](int btnId)
 	{
 		if (btnId < 0)
 			mapGenOptions.setMonsterStrength(EMonsterStrength::RANDOM);
 		else
 			mapGenOptions.setMonsterStrength(static_cast<EMonsterStrength::EMonsterStrength>(btnId + EMonsterStrength::GLOBAL_WEAK)); //value 2 to 4
-	};
+	});
 
 	// Show random maps btn
-	showRandMaps = new CAdventureMapButton("", CGI->generaltexth->zelp[252].second, 0, 54, 535, "RANSHOW");
+	showRandMaps = new CButton(Point(54, 535), "RANSHOW", CGI->generaltexth->zelp[252]);
 
 	// Initialize map info object
 	updateMapInfo();
 }
 
-void CRandomMapTab::addButtonsWithRandToGroup(CHighlightableButtonsGroup * group, const std::vector<std::string> & defs, int nStart, int nEnd, int btnWidth, int helpStartIndex, int helpRandIndex) const
+void CRandomMapTab::addButtonsWithRandToGroup(CToggleGroup * group, const std::vector<std::string> & defs, int nStart, int nEnd, int btnWidth, int helpStartIndex, int helpRandIndex) const
 {
 	addButtonsToGroup(group, defs, nStart, nEnd, btnWidth, helpStartIndex);
 
 	// Buttons are relative to button group, TODO better solution?
 	SObjectConstruction obj__i(group);
 	const std::string RANDOM_DEF = "RANRAND";
-	group->addButton(new CHighlightableButton("", CGI->generaltexth->zelp[helpRandIndex].second, 0, 256, 0, RANDOM_DEF, CMapGenOptions::RANDOM_SIZE));
-	group->select(CMapGenOptions::RANDOM_SIZE, true);
+	group->addToggle(CMapGenOptions::RANDOM_SIZE, new CToggleButton(Point(256, 0), RANDOM_DEF, CGI->generaltexth->zelp[helpRandIndex]));
+	group->setSelected(CMapGenOptions::RANDOM_SIZE);
 }
 
-void CRandomMapTab::addButtonsToGroup(CHighlightableButtonsGroup * group, const std::vector<std::string> & defs, int nStart, int nEnd, int btnWidth, int helpStartIndex) const
+void CRandomMapTab::addButtonsToGroup(CToggleGroup * group, const std::vector<std::string> & defs, int nStart, int nEnd, int btnWidth, int helpStartIndex) const
 {
 	// Buttons are relative to button group, TODO better solution?
 	SObjectConstruction obj__i(group);
 	int cnt = nEnd - nStart + 1;
 	for(int i = 0; i < cnt; ++i)
 	{
-		group->addButton(new CHighlightableButton("", CGI->generaltexth->zelp[helpStartIndex + i].second, 0, i * btnWidth, 0, defs[i + nStart], i + nStart));
+		auto button = new CToggleButton(Point(i * btnWidth, 0), defs[i + nStart], CGI->generaltexth->zelp[helpStartIndex + i]);
+		// For blocked state we should use pressed image actually
+		button->setImageOrder(0, 1, 1, 3);
+
+		group->addToggle(i + nStart, button);
 	}
 }
 
-void CRandomMapTab::deactivateButtonsFrom(CHighlightableButtonsGroup * group, int startId)
+void CRandomMapTab::deactivateButtonsFrom(CToggleGroup * group, int startId)
 {
-	for(CHighlightableButton * btn : group->buttons)
+	for(auto toggle : group->buttons)
 	{
-		if(startId == CMapGenOptions::RANDOM_SIZE || btn->ID < startId)
+		if (auto button = dynamic_cast<CToggleButton*>(toggle.second))
 		{
-			if(btn->isBlocked())
+			if(startId == CMapGenOptions::RANDOM_SIZE || toggle.first < startId)
 			{
-				btn->setOffset(0);
-				btn->setState(CButtonBase::NORMAL);
+				button->block(false);
+			}
+			else
+			{
+				button->block(true);
 			}
-		}
-		else
-		{
-			// Blocked state looks like frame 'selected'=1
-			btn->setOffset(-1);
-			btn->setState(CButtonBase::BLOCKED);
 		}
 	}
 }
@@ -1788,12 +1785,12 @@ void CRandomMapTab::validatePlayersCnt(int playersCnt)
 	if(mapGenOptions.getTeamCount() >= playersCnt)
 	{
 		mapGenOptions.setTeamCount(playersCnt - 1);
-		teamsCntGroup->select(mapGenOptions.getTeamCount(), true);
+		teamsCntGroup->setSelected(mapGenOptions.getTeamCount());
 	}
 	if(mapGenOptions.getCompOnlyPlayerCount() > 8 - playersCnt)
 	{
 		mapGenOptions.setCompOnlyPlayerCount(8 - playersCnt);
-		compOnlyPlayersCntGroup->select(mapGenOptions.getCompOnlyPlayerCount(), true);
+		compOnlyPlayersCntGroup->setSelected(mapGenOptions.getCompOnlyPlayerCount());
 	}
 
 	validateCompOnlyPlayersCnt(mapGenOptions.getCompOnlyPlayerCount());
@@ -1809,7 +1806,7 @@ void CRandomMapTab::validateCompOnlyPlayersCnt(int compOnlyPlayersCnt)
 	if(mapGenOptions.getCompOnlyTeamCount() >= compOnlyPlayersCnt)
 	{
 		mapGenOptions.setCompOnlyTeamCount(compOnlyPlayersCnt - 1);
-		compOnlyTeamsCntGroup->select(mapGenOptions.getCompOnlyTeamCount(), true);
+		compOnlyTeamsCntGroup->setSelected(mapGenOptions.getCompOnlyTeamCount());
 	}
 }
 
@@ -1964,20 +1961,20 @@ InfoCard::InfoCard( bool Network )
 		pos.h = bg->pos.h;
 		sizes = CDefHandler::giveDef("SCNRMPSZ.DEF");
 		sFlags = CDefHandler::giveDef("ITGFLAGS.DEF");
-		difficulty = new CHighlightableButtonsGroup(0);
+		difficulty = new CToggleGroup(0);
 		{
 			static const char *difButns[] = {"GSPBUT3.DEF", "GSPBUT4.DEF", "GSPBUT5.DEF", "GSPBUT6.DEF", "GSPBUT7.DEF"};
 
 			for(int i = 0; i < 5; i++)
 			{
-				difficulty->addButton(new CHighlightableButton("", CGI->generaltexth->zelp[24+i].second, 0, 110 + i*32, 450, difButns[i], i));
+				auto button = new CToggleButton(Point(110 + i*32, 450), difButns[i], CGI->generaltexth->zelp[24+i]);
+
+				difficulty->addToggle(i, button);
+				if(SEL->screenType != CMenuScreen::newGame)
+					button->block(true);
 			}
 		}
 
-		if(SEL->screenType != CMenuScreen::newGame)
-			difficulty->block(true);
-
-
 		if(network)
 		{
 			playerListBg = new CPicture("CHATPLUG.bmp", 16, 276);
@@ -2165,8 +2162,8 @@ void InfoCard::changeSelection( const CMapInfo *to )
 			mapDescription->setText(to->mapHeader->description);
 
 		if(SEL->screenType != CMenuScreen::newGame && SEL->screenType != CMenuScreen::campaignList) {
-			difficulty->block(true);
-			difficulty->select(SEL->sInfo.difficulty, 0);
+			//difficulty->block(true);
+			difficulty->setSelected(SEL->sInfo.difficulty);
 		}
 	}
 	redraw();
@@ -2550,12 +2547,12 @@ OptionsTab::PlayerOptionsEntry::PlayerOptionsEntry( OptionsTab *owner, PlayerSet
 	bg = new CPicture(BitmapHandler::loadBitmap(bgs[s.color.getNum()]), 0, 0, true);
 	if(SEL->screenType == CMenuScreen::newGame)
 	{
-		btns[0] = new CAdventureMapButton(CGI->generaltexth->zelp[132], boost::bind(&OptionsTab::nextCastle, owner, s.color, -1), 107, 5, "ADOPLFA.DEF");
-		btns[1] = new CAdventureMapButton(CGI->generaltexth->zelp[133], boost::bind(&OptionsTab::nextCastle, owner, s.color, +1), 168, 5, "ADOPRTA.DEF");
-		btns[2] = new CAdventureMapButton(CGI->generaltexth->zelp[148], boost::bind(&OptionsTab::nextHero, owner, s.color, -1), 183, 5, "ADOPLFA.DEF");
-		btns[3] = new CAdventureMapButton(CGI->generaltexth->zelp[149], boost::bind(&OptionsTab::nextHero, owner, s.color, +1), 244, 5, "ADOPRTA.DEF");
-		btns[4] = new CAdventureMapButton(CGI->generaltexth->zelp[164], boost::bind(&OptionsTab::nextBonus, owner, s.color, -1), 259, 5, "ADOPLFA.DEF");
-		btns[5] = new CAdventureMapButton(CGI->generaltexth->zelp[165], boost::bind(&OptionsTab::nextBonus, owner, s.color, +1), 320, 5, "ADOPRTA.DEF");
+		btns[0] = new CButton(Point(107, 5), "ADOPLFA.DEF", CGI->generaltexth->zelp[132], boost::bind(&OptionsTab::nextCastle, owner, s.color, -1));
+		btns[1] = new CButton(Point(168, 5), "ADOPRTA.DEF", CGI->generaltexth->zelp[133], boost::bind(&OptionsTab::nextCastle, owner, s.color, +1));
+		btns[2] = new CButton(Point(183, 5), "ADOPLFA.DEF", CGI->generaltexth->zelp[148], boost::bind(&OptionsTab::nextHero, owner, s.color, -1));
+		btns[3] = new CButton(Point(244, 5), "ADOPRTA.DEF", CGI->generaltexth->zelp[149], boost::bind(&OptionsTab::nextHero, owner, s.color, +1));
+		btns[4] = new CButton(Point(259, 5), "ADOPLFA.DEF", CGI->generaltexth->zelp[164], boost::bind(&OptionsTab::nextBonus, owner, s.color, -1));
+		btns[5] = new CButton(Point(320, 5), "ADOPRTA.DEF", CGI->generaltexth->zelp[165], boost::bind(&OptionsTab::nextBonus, owner, s.color, +1));
 	}
 	else
 		for(auto & elem : btns)
@@ -2577,7 +2574,7 @@ OptionsTab::PlayerOptionsEntry::PlayerOptionsEntry( OptionsTab *owner, PlayerSet
 		&&  SEL->current->mapHeader->players[s.color.getNum()].canHumanPlay
 		&&  SEL->multiPlayer != CMenuScreen::MULTI_NETWORK_GUEST)
 	{
-		flag = new CAdventureMapButton(CGI->generaltexth->zelp[180], boost::bind(&OptionsTab::flagPressed, owner, s.color), -43, 2, flags[s.color.getNum()]);
+		flag = new CButton(Point(-43, 2), flags[s.color.getNum()], CGI->generaltexth->zelp[180], boost::bind(&OptionsTab::flagPressed, owner, s.color));
 		flag->hoverable = true;
 	}
 	else
@@ -2983,8 +2980,8 @@ CScenarioInfo::CScenarioInfo(const CMapHeader *mapHeader, const StartInfo *start
 	opt->recreate();
 	card->changeSelection(current);
 
-	card->difficulty->select(startInfo->difficulty, 0);
-	back = new CAdventureMapButton("", CGI->generaltexth->zelp[105].second, boost::bind(&CGuiHandler::popIntTotally, &GH, this), 584, 535, "SCNRBACK.DEF", SDLK_ESCAPE);
+	card->difficulty->setSelected(startInfo->difficulty);
+	back = new CButton(Point(584, 535), "SCNRBACK.DEF", CGI->generaltexth->zelp[105], boost::bind(&CGuiHandler::popIntTotally, &GH, this), SDLK_ESCAPE);
 }
 
 CScenarioInfo::~CScenarioInfo()
@@ -3063,10 +3060,10 @@ CMultiMode::CMultiMode()
 	txt = new CTextInput(Rect(19, 436, 334, 16), *bg);
 	txt->setText(settings["general"]["playerName"].String()); //Player
 
-	btns[0] = new CAdventureMapButton(CGI->generaltexth->zelp[266], boost::bind(&CMultiMode::openHotseat, this), 373, 78, "MUBHOT.DEF");
-	btns[1] = new CAdventureMapButton("Host TCP/IP game", "", boost::bind(&CMultiMode::hostTCP, this), 373, 78 + 57*1, "MUBHOST.DEF");
-	btns[2] = new CAdventureMapButton("Join TCP/IP game", "", boost::bind(&CMultiMode::joinTCP, this), 373, 78 + 57*2, "MUBJOIN.DEF");
-	btns[6] = new CAdventureMapButton(CGI->generaltexth->zelp[288], boost::bind(&CGuiHandler::popIntTotally, boost::ref(GH), this), 373, 424, "MUBCANC.DEF", SDLK_ESCAPE);
+	btns[0] = new CButton(Point(373, 78),        "MUBHOT.DEF",  CGI->generaltexth->zelp[266], boost::bind(&CMultiMode::openHotseat, this));
+	btns[1] = new CButton(Point(373, 78 + 57*1), "MUBHOST.DEF", CButton::tooltip("Host TCP/IP game", ""), boost::bind(&CMultiMode::hostTCP, this));
+	btns[2] = new CButton(Point(373, 78 + 57*2), "MUBJOIN.DEF", CButton::tooltip("Join TCP/IP game", ""), boost::bind(&CMultiMode::joinTCP, this));
+	btns[6] = new CButton(Point(373, 424),       "MUBCANC.DEF", CGI->generaltexth->zelp[288], [&] { GH.popIntTotally(this);}, SDLK_ESCAPE);
 }
 
 void CMultiMode::openHotseat()
@@ -3106,8 +3103,8 @@ CHotSeatPlayers::CHotSeatPlayers(const std::string &firstPlayer)
 		txt[i]->cb += boost::bind(&CHotSeatPlayers::onChange, this, _1);
 	}
 
-	ok = new CAdventureMapButton(CGI->generaltexth->zelp[560], boost::bind(&CHotSeatPlayers::enterSelectionScreen, this), 95, 338, "MUBCHCK.DEF", SDLK_RETURN);
-	cancel = new CAdventureMapButton(CGI->generaltexth->zelp[561], boost::bind(&CGuiHandler::popIntTotally, boost::ref(GH), this), 205, 338, "MUBCANC.DEF", SDLK_ESCAPE);
+	ok = new CButton(Point(95, 338), "MUBCHCK.DEF", CGI->generaltexth->zelp[560], boost::bind(&CHotSeatPlayers::enterSelectionScreen, this), SDLK_RETURN);
+	cancel = new CButton(Point(205, 338), "MUBCANC.DEF", CGI->generaltexth->zelp[561], boost::bind(&CGuiHandler::popIntTotally, boost::ref(GH), this), SDLK_ESCAPE);
 	bar = new CGStatusBar(new CPicture(Rect(7, 381, 348, 18), 0));//226, 472
 
 	txt[0]->setText(firstPlayer, true);
@@ -3170,9 +3167,9 @@ void CBonusSelection::init()
 
 	blitAt(panel, 456, 6, background);
 
-	startB = new CAdventureMapButton("", "", boost::bind(&CBonusSelection::startMap, this), 475, 536, "CBBEGIB.DEF", SDLK_RETURN);
-	restartB = new CAdventureMapButton("", "", boost::bind(&CBonusSelection::restartMap, this), 475, 536, "CBRESTB.DEF", SDLK_RETURN);
-	backB = new CAdventureMapButton("", "", boost::bind(&CBonusSelection::goBack, this), 624, 536, "CBCANCB.DEF", SDLK_ESCAPE);
+	startB   = new CButton(Point(475, 536), "CBBEGIB.DEF", CButton::tooltip(), boost::bind(&CBonusSelection::startMap, this), SDLK_RETURN);
+	restartB = new CButton(Point(475, 536), "CBRESTB.DEF", CButton::tooltip(), boost::bind(&CBonusSelection::restartMap, this), SDLK_RETURN);
+	backB    = new CButton(Point(624, 536), "CBCANCB.DEF", CButton::tooltip(), boost::bind(&CBonusSelection::goBack, this), SDLK_ESCAPE);
 
 	//campaign name
 	if (ourCampaign->camp->header.name.length())
@@ -3194,7 +3191,7 @@ void CBonusSelection::init()
 
 	//bonus choosing
 	graphics->fonts[FONT_MEDIUM]->renderTextLeft(background, CGI->generaltexth->allTexts[71], Colors::WHITE, Point(511, 432));
-	bonuses = new CHighlightableButtonsGroup(bind(&CBonusSelection::selectBonus, this, _1));
+	bonuses = new CToggleGroup(bind(&CBonusSelection::selectBonus, this, _1));
 
 	//set left part of window
 	bool isCurrentMapConquerable = ourCampaign->currentMap && ourCampaign->camp->conquerable(*ourCampaign->currentMap);
@@ -3245,8 +3242,8 @@ void CBonusSelection::init()
 	//difficulty selection buttons
 	if (ourCampaign->camp->header.difficultyChoosenByPlayer)
 	{
-		diffLb = new CAdventureMapButton("", "", boost::bind(&CBonusSelection::decreaseDifficulty, this), 694, 508, "SCNRBLF.DEF");
-		diffRb = new CAdventureMapButton("", "", boost::bind(&CBonusSelection::increaseDifficulty, this), 738, 508, "SCNRBRT.DEF");
+		diffLb = new CButton(Point(694, 508), "SCNRBLF.DEF", CButton::tooltip(), boost::bind(&CBonusSelection::decreaseDifficulty, this));
+		diffRb = new CButton(Point(738, 508), "SCNRBRT.DEF", CButton::tooltip(), boost::bind(&CBonusSelection::increaseDifficulty, this));
 	}
 
 	//load miniflags
@@ -3446,13 +3443,8 @@ void CBonusSelection::updateBonusSelection()
 
 	updateStartButtonState(-1);
 
-	for (auto & elem : bonuses->buttons)
-	{
-		if (elem->active)
-			elem->deactivate();
-		delete elem;
-	}
-	bonuses->buttons.clear();
+	delete bonuses;
+	bonuses = new CToggleGroup(bind(&CBonusSelection::selectBonus, this, _1));
 
 	static const char *bonusPics[] = {"SPELLBON.DEF", "TWCRPORT.DEF", "", "ARTIFBON.DEF", "SPELLBON.DEF",
 		"PSKILBON.DEF", "SSKILBON.DEF", "BORES.DEF", "PORTRAITSLARGE", "PORTRAITSLARGE"};
@@ -3606,7 +3598,7 @@ void CBonusSelection::updateBonusSelection()
 			break;
 		}
 
-		CHighlightableButton *bonusButton = new CHighlightableButton(desc, desc, 0, 475 + i*68, 455, "", i);
+		CToggleButton *bonusButton = new CToggleButton(Point(475 + i*68, 455), "", CButton::tooltip(desc, desc));
 
 		if (picNumber != -1)
 			picName += ":" + boost::lexical_cast<std::string>(picNumber);
@@ -3616,13 +3608,13 @@ void CBonusSelection::updateBonusSelection()
 		bonusButton->setImage(anim);
 		const SDL_Color brightYellow = { 242, 226, 110, 0 };
 		bonusButton->borderColor = brightYellow;
-		bonuses->addButton(bonusButton);
+		bonuses->addToggle(i, bonusButton);
 	}
 
 	// set bonus if already chosen
 	if(vstd::contains(ourCampaign->chosenCampaignBonuses, selectedMap))
 	{
-		bonuses->select(ourCampaign->chosenCampaignBonuses[selectedMap], false);
+		bonuses->setSelected(ourCampaign->chosenCampaignBonuses[selectedMap]);
 	}
 }
 
@@ -3731,11 +3723,11 @@ void CBonusSelection::updateStartButtonState(int selected /*= -1*/)
 {
 	if(selected == -1)
 	{
-		startB->setState(ourCampaign->camp->scenarios[selectedMap].travelOptions.bonusesToChoose.size() ? CButtonBase::BLOCKED : CButtonBase::NORMAL);
+		startB->block(ourCampaign->camp->scenarios[selectedMap].travelOptions.bonusesToChoose.size());
 	}
-	else if(startB->getState() == CButtonBase::BLOCKED)
+	else if(startB->isBlocked())
 	{
-		startB->setState(CButtonBase::NORMAL);
+		startB->block(false);
 	}
 }
 
@@ -4095,14 +4087,14 @@ void CCampaignScreen::CCampaignButton::show(SDL_Surface * to)
 	}
 }
 
-CAdventureMapButton* CCampaignScreen::createExitButton(const JsonNode& button)
+CButton* CCampaignScreen::createExitButton(const JsonNode& button)
 {
 	std::pair<std::string, std::string> help;
 	if (!button["help"].isNull() && button["help"].Float() > 0)
 		help = CGI->generaltexth->zelp[button["help"].Float()];
 
 	std::function<void()> close = boost::bind(&CGuiHandler::popIntTotally, &GH, this);
-	return new CAdventureMapButton(help, close, button["x"].Float(), button["y"].Float(), button["name"].String(), button["hotkey"].Float());
+	return new CButton(Point(button["x"].Float(), button["y"].Float()), button["name"].String(), help, close, button["hotkey"].Float());
 }
 
 
@@ -4243,8 +4235,8 @@ CSimpleJoinScreen::CSimpleJoinScreen()
 	port->cb += boost::bind(&CSimpleJoinScreen::onChange, this, _1);
 	port->filters.add(boost::bind(&CTextInput::numberFilter, _1, _2, 0, 65535));
 
-	ok = new CAdventureMapButton(CGI->generaltexth->zelp[560], boost::bind(&CSimpleJoinScreen::enterSelectionScreen, this), 26, 142, "MUBCHCK.DEF", SDLK_RETURN);
-	cancel = new CAdventureMapButton(CGI->generaltexth->zelp[561], boost::bind(&CGuiHandler::popIntTotally, boost::ref(GH), this), 142, 142, "MUBCANC.DEF", SDLK_ESCAPE);
+	ok     = new CButton(Point( 26, 142), "MUBCHCK.DEF", CGI->generaltexth->zelp[560], boost::bind(&CSimpleJoinScreen::enterSelectionScreen, this), SDLK_RETURN);
+	cancel = new CButton(Point(142, 142), "MUBCANC.DEF", CGI->generaltexth->zelp[561], boost::bind(&CGuiHandler::popIntTotally, boost::ref(GH), this), SDLK_ESCAPE);
 	bar = new CGStatusBar(new CPicture(Rect(7, 186, 218, 18), 0));
 
 	port->setText(boost::lexical_cast<std::string>(settings["server"]["port"].Float()), true);

+ 24 - 24
client/CPreGame.h

@@ -33,10 +33,10 @@ class CRandomMapTab;
 struct CPackForSelectionScreen;
 struct PlayerInfo;
 class CMultiLineLabel;
-class CHighlightableButton;
-class CHighlightableButtonsGroup;
+class CToggleButton;
+class CToggleGroup;
 class CTabbedInt;
-class CAdventureMapButton;
+class CButton;
 class CSlider;
 
 namespace boost{ class thread; class recursive_mutex;}
@@ -86,9 +86,9 @@ public:
 class CMenuEntry : public CIntObject
 {
 	std::vector<CPicture*> images;
-	std::vector<CAdventureMapButton*> buttons;
+	std::vector<CButton*> buttons;
 
-	CAdventureMapButton* createButton(CMenuScreen* parent, const JsonNode& button);
+	CButton* createButton(CMenuScreen* parent, const JsonNode& button);
 public:
 	CMenuEntry(CMenuScreen* parent, const JsonNode &config);
 };
@@ -132,7 +132,7 @@ public:
 	CChatBox *chat;
 	CPicture *playerListBg;
 
-	CHighlightableButtonsGroup *difficulty;
+	CToggleGroup *difficulty;
 	CDefHandler *sizes, *sFlags;
 
 	void changeSelection(const CMapInfo *to);
@@ -245,8 +245,8 @@ public:
 		PlayerInfo &pi;
 		PlayerSettings &s;
 		CPicture *bg;
-		CAdventureMapButton *btns[6]; //left and right for town, hero, bonus
-		CAdventureMapButton *flag;
+		CButton *btns[6]; //left and right for town, hero, bonus
+		CButton *flag;
 		SelectedBox *town;
 		SelectedBox *hero;
 		SelectedBox *bonus;
@@ -302,17 +302,17 @@ public:
 	const CMapGenOptions & getMapGenOptions() const;
 
 private:
-    void addButtonsToGroup(CHighlightableButtonsGroup * group, const std::vector<std::string> & defs, int startIndex, int endIndex, int btnWidth, int helpStartIndex) const;
-    void addButtonsWithRandToGroup(CHighlightableButtonsGroup * group, const std::vector<std::string> & defs, int startIndex, int endIndex, int btnWidth, int helpStartIndex, int helpRandIndex) const;
-    void deactivateButtonsFrom(CHighlightableButtonsGroup * group, int startId);
+    void addButtonsToGroup(CToggleGroup * group, const std::vector<std::string> & defs, int startIndex, int endIndex, int btnWidth, int helpStartIndex) const;
+    void addButtonsWithRandToGroup(CToggleGroup * group, const std::vector<std::string> & defs, int startIndex, int endIndex, int btnWidth, int helpStartIndex, int helpRandIndex) const;
+    void deactivateButtonsFrom(CToggleGroup * group, int startId);
     void validatePlayersCnt(int playersCnt);
     void validateCompOnlyPlayersCnt(int compOnlyPlayersCnt);
 
     CPicture * bg;
-	CHighlightableButton * twoLevelsBtn;
-	CHighlightableButtonsGroup * mapSizeBtnGroup, * playersCntGroup, * teamsCntGroup, * compOnlyPlayersCntGroup,
+	CToggleButton * twoLevelsBtn;
+	CToggleGroup * mapSizeBtnGroup, * playersCntGroup, * teamsCntGroup, * compOnlyPlayersCntGroup,
 		* compOnlyTeamsCntGroup, * waterContentGroup, * monsterStrengthGroup;
-    CAdventureMapButton * showRandMaps;
+    CButton * showRandMaps;
 	CMapGenOptions mapGenOptions;
 	unique_ptr<CMapInfo> mapInfo;
 	CFunctionList<void(const CMapInfo *)> mapInfoChanged;
@@ -353,7 +353,7 @@ public:
 	InfoCard *card;
 	OptionsTab *opt;
 	CRandomMapTab * randMapTab;
-	CAdventureMapButton *start, *back;
+	CButton *start, *back;
 
 	SelectionTab *sel;
 	CIntObject *curTab;
@@ -401,7 +401,7 @@ public:
 class CScenarioInfo : public CIntObject, public ISelectionScreenInfo
 {
 public:
-	CAdventureMapButton *back;
+	CButton *back;
 	InfoCard *card;
 	OptionsTab *opt;
 
@@ -415,7 +415,7 @@ class CMultiMode : public CIntObject
 public:
 	CPicture *bg;
 	CTextInput *txt;
-	CAdventureMapButton *btns[7]; //0 - hotseat, 6 - cancel
+	CButton *btns[7]; //0 - hotseat, 6 - cancel
 	CGStatusBar *bar;
 
 	CMultiMode();
@@ -430,7 +430,7 @@ class CHotSeatPlayers : public CIntObject
 	CPicture *bg;
 	CTextBox *title;
 	CTextInput* txt[8];
-	CAdventureMapButton *ok, *cancel;
+	CButton *ok, *cancel;
 	CGStatusBar *bar;
 
 	void onChange(std::string newText);
@@ -518,14 +518,14 @@ private:
 
 	// GUI components
 	SDL_Surface * background;
-	CAdventureMapButton * startB, * restartB, * backB;
+	CButton * startB, * restartB, * backB;
 	CTextBox * campaignDescription, * mapDescription;
 	std::vector<SCampPositions> campDescriptions;
 	std::vector<CRegion *> regions;
 	CRegion * highlightedRegion;
-	CHighlightableButtonsGroup * bonuses;
+	CToggleGroup * bonuses;
 	SDL_Surface * diffPics[5]; //pictures of difficulties, user-selectable (or not if campaign locks this)
-	CAdventureMapButton * diffLb, * diffRb; //buttons for changing difficulty
+	CButton * diffLb, * diffRb; //buttons for changing difficulty
 	CDefHandler * sizes; //icons of map sizes
 	CDefHandler * sFlags;
 
@@ -566,11 +566,11 @@ private:
 		void show(SDL_Surface * to);
 	};
 
-	CAdventureMapButton *back;
+	CButton *back;
 	std::vector<CCampaignButton*> campButtons;
 	std::vector<CPicture*> images;
 
-	CAdventureMapButton* createExitButton(const JsonNode& button);
+	CButton* createExitButton(const JsonNode& button);
 
 public:
 	enum CampaignSet {ROE, AB, SOD, WOG};
@@ -636,7 +636,7 @@ class CSimpleJoinScreen : public CIntObject
 {
 	CPicture * bg;
 	CTextBox * title;
-	CAdventureMapButton * ok, * cancel;
+	CButton * ok, * cancel;
 	CGStatusBar * bar;
 	CTextInput * address;
 	CTextInput * port;

+ 12 - 12
client/battle/CBattleInterface.cpp

@@ -222,17 +222,17 @@ CBattleInterface::CBattleInterface(const CCreatureSet * army1, const CCreatureSe
 // 	blitAt(menu, pos.x, 556 + pos.y);
 
 	//preparing buttons and console
-	bOptions = new CAdventureMapButton (CGI->generaltexth->zelp[381].first, CGI->generaltexth->zelp[381].second, boost::bind(&CBattleInterface::bOptionsf,this), 3, 561, "icm003.def", SDLK_o);
-	bSurrender = new CAdventureMapButton (CGI->generaltexth->zelp[379].first, CGI->generaltexth->zelp[379].second, boost::bind(&CBattleInterface::bSurrenderf,this), 54, 561, "icm001.def", SDLK_s);
-	bFlee = new CAdventureMapButton (CGI->generaltexth->zelp[380].first, CGI->generaltexth->zelp[380].second, boost::bind(&CBattleInterface::bFleef,this), 105, 561, "icm002.def", SDLK_r);
-	bAutofight  = new CAdventureMapButton (CGI->generaltexth->zelp[382].first, CGI->generaltexth->zelp[382].second, boost::bind(&CBattleInterface::bAutofightf,this), 157, 561, "icm004.def", SDLK_a);
-	bSpell = new CAdventureMapButton (CGI->generaltexth->zelp[385].first, CGI->generaltexth->zelp[385].second, boost::bind(&CBattleInterface::bSpellf,this), 645, 561, "icm005.def", SDLK_c);
-	bWait = new CAdventureMapButton (CGI->generaltexth->zelp[386].first, CGI->generaltexth->zelp[386].second, boost::bind(&CBattleInterface::bWaitf,this), 696, 561, "icm006.def", SDLK_w);
-	bDefence = new CAdventureMapButton (CGI->generaltexth->zelp[387].first, CGI->generaltexth->zelp[387].second, boost::bind(&CBattleInterface::bDefencef,this), 747, 561, "icm007.def", SDLK_d);
+	bOptions =     new CButton (Point(  3, 561), "icm003.def", CGI->generaltexth->zelp[381], boost::bind(&CBattleInterface::bOptionsf,this), SDLK_o);
+	bSurrender =   new CButton (Point( 54, 561), "icm001.def", CGI->generaltexth->zelp[379], boost::bind(&CBattleInterface::bSurrenderf,this), SDLK_s);
+	bFlee =        new CButton (Point(105, 561), "icm002.def", CGI->generaltexth->zelp[380], boost::bind(&CBattleInterface::bFleef,this), SDLK_r);
+	bAutofight  =  new CButton (Point(157, 561), "icm004.def", CGI->generaltexth->zelp[382], boost::bind(&CBattleInterface::bAutofightf,this), SDLK_a);
+	bSpell =       new CButton (Point(645, 561), "icm005.def", CGI->generaltexth->zelp[385], boost::bind(&CBattleInterface::bSpellf,this), SDLK_c);
+	bWait =        new CButton (Point(696, 561), "icm006.def", CGI->generaltexth->zelp[386], boost::bind(&CBattleInterface::bWaitf,this), SDLK_w);
+	bDefence =     new CButton (Point(747, 561), "icm007.def", CGI->generaltexth->zelp[387], boost::bind(&CBattleInterface::bDefencef,this), SDLK_d);
 	bDefence->assignedKeys.insert(SDLK_SPACE);
-	bConsoleUp = new CAdventureMapButton (std::string(), std::string(), boost::bind(&CBattleInterface::bConsoleUpf,this), 624, 561, "ComSlide.def", SDLK_UP);
-	bConsoleDown = new CAdventureMapButton (std::string(), std::string(), boost::bind(&CBattleInterface::bConsoleDownf,this), 624, 580, "ComSlide.def", SDLK_DOWN);
-	bConsoleDown->setOffset(2);
+	bConsoleUp =   new CButton (Point(624, 561), "ComSlide.def", std::make_pair("", ""), boost::bind(&CBattleInterface::bConsoleUpf,this), SDLK_UP);
+	bConsoleDown = new CButton (Point(624, 580), "ComSlide.def", std::make_pair("", ""), boost::bind(&CBattleInterface::bConsoleDownf,this), SDLK_DOWN);
+	bConsoleDown->setImageOrder(2, 3, 4, 5);
 	console = new CBattleConsole();
 	console->pos.x += 211;
 	console->pos.y += 560;
@@ -240,8 +240,8 @@ CBattleInterface::CBattleInterface(const CCreatureSet * army1, const CCreatureSe
 	console->pos.h = 38;
 	if(tacticsMode)
 	{
-		btactNext = new CAdventureMapButton(std::string(), std::string(), boost::bind(&CBattleInterface::bTacticNextStack,this, (CStack*)nullptr), 213, 560, "icm011.def", SDLK_SPACE);
-		btactEnd = new CAdventureMapButton(std::string(), std::string(), boost::bind(&CBattleInterface::bEndTacticPhase,this), 419, 560, "icm012.def", SDLK_RETURN);
+		btactNext = new CButton(Point(213, 560), "icm011.def", std::make_pair("", ""), [&]{ bTacticNextStack(nullptr);}, SDLK_SPACE);
+		btactEnd =  new CButton(Point(419, 560), "icm012.def", std::make_pair("", ""), [&]{ bEndTacticPhase();}, SDLK_RETURN);
 		menu = BitmapHandler::loadBitmap("COPLACBR.BMP");
 	}
 	else

+ 5 - 5
client/battle/CBattleInterface.h

@@ -23,9 +23,9 @@ class CGHeroInstance;
 class CDefHandler;
 class CStack;
 class CCallback;
-class CAdventureMapButton;
-class CHighlightableButton;
-class CHighlightableButtonsGroup;
+class CButton;
+class CToggleButton;
+class CToggleGroup;
 struct BattleResult;
 struct BattleSpellCast;
 struct CObstacleInstance;
@@ -120,7 +120,7 @@ class CBattleInterface : public CIntObject
 	};
 private:
 	SDL_Surface * background, * menu, * amountNormal, * amountNegative, * amountPositive, * amountEffNeutral, * cellBorders, * backgroundWithHexes;
-	CAdventureMapButton * bOptions, * bSurrender, * bFlee, * bAutofight, * bSpell,
+	CButton * bOptions, * bSurrender, * bFlee, * bAutofight, * bSpell,
 		* bWait, * bDefence, * bConsoleUp, * bConsoleDown, *btactNext, *btactEnd;
 	CBattleConsole * console;
 	CBattleHero * attackingHero, * defendingHero; //fighting heroes
@@ -338,7 +338,7 @@ public:
 	InfoAboutHero enemyHero() const;
 
 	friend class CPlayerInterface;
-	friend class CAdventureMapButton;
+	friend class CButton;
 	friend class CInGameConsole;
 	
 	friend class CBattleResultWindow;

+ 19 - 22
client/battle/CBattleInterfaceClasses.cpp

@@ -263,26 +263,23 @@ CBattleOptionsWindow::CBattleOptionsWindow(const SDL_Rect & position, CBattleInt
 	background = new CPicture("comopbck.bmp");
 	background->colorize(owner->getCurrentPlayerInterface()->playerID);
 
-	viewGrid = new CHighlightableButton(boost::bind(&CBattleInterface::setPrintCellBorders, owner, true), boost::bind(&CBattleInterface::setPrintCellBorders, owner, false), boost::assign::map_list_of(0,CGI->generaltexth->zelp[427].first)(3,CGI->generaltexth->zelp[427].first), CGI->generaltexth->zelp[427].second, false, "sysopchk.def", nullptr, 25, 56, false);
-	viewGrid->select(settings["battle"]["cellBorders"].Bool());
-	movementShadow = new CHighlightableButton(boost::bind(&CBattleInterface::setPrintStackRange, owner, true), boost::bind(&CBattleInterface::setPrintStackRange, owner, false), boost::assign::map_list_of(0,CGI->generaltexth->zelp[428].first)(3,CGI->generaltexth->zelp[428].first), CGI->generaltexth->zelp[428].second, false, "sysopchk.def", nullptr, 25, 89, false);
-	movementShadow->select(settings["battle"]["stackRange"].Bool());
-	mouseShadow = new CHighlightableButton(boost::bind(&CBattleInterface::setPrintMouseShadow, owner, true), boost::bind(&CBattleInterface::setPrintMouseShadow, owner, false), boost::assign::map_list_of(0,CGI->generaltexth->zelp[429].first)(3,CGI->generaltexth->zelp[429].first), CGI->generaltexth->zelp[429].second, false, "sysopchk.def", nullptr, 25, 122, false);
-	mouseShadow->select(settings["battle"]["mouseShadow"].Bool());
-
-	animSpeeds = new CHighlightableButtonsGroup(0);
-	animSpeeds->addButton(boost::assign::map_list_of(0,CGI->generaltexth->zelp[422].first),CGI->generaltexth->zelp[422].second, "sysopb9.def", 28, 225, 40);
-	animSpeeds->addButton(boost::assign::map_list_of(0,CGI->generaltexth->zelp[423].first),CGI->generaltexth->zelp[423].second, "sysob10.def", 92, 225, 63);
-	animSpeeds->addButton(boost::assign::map_list_of(0,CGI->generaltexth->zelp[424].first),CGI->generaltexth->zelp[424].second, "sysob11.def",156, 225, 100);
-	animSpeeds->select(owner->getAnimSpeed(), 1);
-	animSpeeds->onChange = boost::bind(&CBattleInterface::setAnimSpeed, owner, _1);
-
-	setToDefault = new CAdventureMapButton (CGI->generaltexth->zelp[393], boost::bind(&CBattleOptionsWindow::bDefaultf,this), 246, 359, "codefaul.def");
-	setToDefault->swappedImages = true;
-	setToDefault->update();
-	exit = new CAdventureMapButton (CGI->generaltexth->zelp[392], boost::bind(&CBattleOptionsWindow::bExitf,this), 357, 359, "soretrn.def",SDLK_RETURN);
-	exit->swappedImages = true;
-	exit->update();
+	viewGrid = new CToggleButton(Point(25, 56), "sysopchk.def", CGI->generaltexth->zelp[427], [&](bool on){owner->setPrintCellBorders(on);} );
+	viewGrid->setSelected(settings["battle"]["cellBorders"].Bool());
+	movementShadow = new CToggleButton(Point(25, 89), "sysopchk.def", CGI->generaltexth->zelp[428], [&](bool on){owner->setPrintStackRange(on);});
+	movementShadow->setSelected(settings["battle"]["stackRange"].Bool());
+	mouseShadow = new CToggleButton(Point(25, 122), "sysopchk.def", CGI->generaltexth->zelp[429], [&](bool on){owner->setPrintMouseShadow(on);});
+	mouseShadow->setSelected(settings["battle"]["mouseShadow"].Bool());
+
+	animSpeeds = new CToggleGroup([&](int value){ owner->setAnimSpeed(value);});
+	animSpeeds->addToggle(40,  new CToggleButton(Point( 28, 225), "sysopb9.def", CGI->generaltexth->zelp[422]));
+	animSpeeds->addToggle(63,  new CToggleButton(Point( 92, 225), "sysob10.def", CGI->generaltexth->zelp[423]));
+	animSpeeds->addToggle(100, new CToggleButton(Point(156, 225), "sysob11.def", CGI->generaltexth->zelp[424]));
+	animSpeeds->setSelected(owner->getAnimSpeed());
+
+	setToDefault = new CButton (Point(246, 359), "codefaul.def", CGI->generaltexth->zelp[393], [&]{ bDefaultf(); });
+	setToDefault->setImageOrder(1, 0, 2, 3);
+	exit = new CButton (Point(357, 359), "soretrn.def", CGI->generaltexth->zelp[392], [&]{ bExitf();}, SDLK_RETURN);
+	exit->setImageOrder(1, 0, 2, 3);
 
 	//creating labels
 	labels.push_back(new CLabel(242,  32, FONT_BIG,    CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[392]));//window title
@@ -312,6 +309,7 @@ CBattleOptionsWindow::CBattleOptionsWindow(const SDL_Rect & position, CBattleInt
 
 void CBattleOptionsWindow::bDefaultf()
 {
+	//TODO: implement
 }
 
 void CBattleOptionsWindow::bExitf()
@@ -327,9 +325,8 @@ CBattleResultWindow::CBattleResultWindow(const BattleResult &br, const SDL_Rect
 	CPicture * bg = new CPicture("CPRESULT");
 	bg->colorize(owner.playerID);
 
-	exit = new CAdventureMapButton ("", "", boost::bind(&CBattleResultWindow::bExitf,this), 384, 505, "iok6432.def", SDLK_RETURN);
+	exit = new CButton (Point(384, 505), "iok6432.def", std::make_pair("", ""), [&]{ bExitf();}, SDLK_RETURN);
 	exit->borderColor = Colors::METALLIC_GOLD;
-	exit->borderEnabled = true;
 
 	if(br.winner==0) //attacker won
 	{

+ 8 - 8
client/battle/CBattleInterfaceClasses.h

@@ -8,9 +8,9 @@ class CDefHandler;
 class CGHeroInstance;
 class CBattleInterface;
 class CPicture;
-class CAdventureMapButton;
-class CHighlightableButton;
-class CHighlightableButtonsGroup;
+class CButton;
+class CToggleButton;
+class CToggleGroup;
 class CLabel;
 struct BattleResult;
 class CStack;
@@ -72,9 +72,9 @@ class CBattleOptionsWindow : public CIntObject
 {
 private:
 	CPicture * background;
-	CAdventureMapButton * setToDefault, * exit;
-	CHighlightableButton * viewGrid, * movementShadow, * mouseShadow;
-	CHighlightableButtonsGroup * animSpeeds;
+	CButton * setToDefault, * exit;
+	CToggleButton * viewGrid, * movementShadow, * mouseShadow;
+	CToggleGroup * animSpeeds;
 
 	std::vector<CLabel*> labels;
 public:
@@ -88,7 +88,7 @@ public:
 class CBattleResultWindow : public CIntObject
 {
 private:
-	CAdventureMapButton *exit;
+	CButton *exit;
 	CPlayerInterface &owner;
 public:
 	CBattleResultWindow(const BattleResult & br, const SDL_Rect & pos, CPlayerInterface &_owner); //c-tor
@@ -152,4 +152,4 @@ public:
 	void update();
 	void showAll(SDL_Surface *to);
 	void blitBg(SDL_Surface * to);
-};
+};

+ 13 - 0
client/gui/CIntObject.cpp

@@ -323,6 +323,19 @@ bool CIntObject::captureThisEvent(const SDL_KeyboardEvent & key)
 	return captureAllKeys;
 }
 
+CKeyShortcut::CKeyShortcut()
+{}
+
+CKeyShortcut::CKeyShortcut(int key)
+{
+	if (key != SDLK_UNKNOWN)
+		assignedKeys.insert(key);
+}
+
+CKeyShortcut::CKeyShortcut(std::set<int> Keys)
+    :assignedKeys(Keys)
+{}
+
 void CKeyShortcut::keyPressed(const SDL_KeyboardEvent & key)
 {
 	if(vstd::contains(assignedKeys,key.keysym.sym)

+ 3 - 3
client/gui/CIntObject.h

@@ -218,8 +218,8 @@ class CKeyShortcut : public virtual CIntObject
 {
 public:
 	std::set<int> assignedKeys;
-	CKeyShortcut(){}; //c-tor
-	CKeyShortcut(int key){assignedKeys.insert(key);}; //c-tor
-	CKeyShortcut(std::set<int> Keys):assignedKeys(Keys){}; //c-tor
+	CKeyShortcut();
+	CKeyShortcut(int key);
+	CKeyShortcut(std::set<int> Keys);
 	virtual void keyPressed(const SDL_KeyboardEvent & key); //call-in
 };

+ 5 - 5
client/widgets/AdventureMapClasses.cpp

@@ -106,15 +106,15 @@ CList::CList(int Size, Point position, std::string btnUp, std::string btnDown, s
     selected(nullptr)
 {
 	OBJ_CONSTRUCTION_CAPTURING_ALL;
-	scrollUp = new CAdventureMapButton(CGI->generaltexth->zelp[helpUp], 0, 0, 0, btnUp);
+	scrollUp = new CButton(Point(0, 0), btnUp, CGI->generaltexth->zelp[helpUp]);
 	list = new CListBox(create, destroy, Point(1,scrollUp->pos.h), Point(0, 32), size, listAmount);
 
 	//assign callback only after list was created
-	scrollUp->callback = boost::bind(&CListBox::moveToPrev, list);
-	scrollDown = new CAdventureMapButton(CGI->generaltexth->zelp[helpDown], boost::bind(&CListBox::moveToNext, list), 0, scrollUp->pos.h + 32*size, btnDown);
+	scrollUp->addCallback(boost::bind(&CListBox::moveToPrev, list));
+	scrollDown = new CButton(Point(0, scrollUp->pos.h + 32*size), btnDown, CGI->generaltexth->zelp[helpDown], boost::bind(&CListBox::moveToNext, list));
 
-	scrollDown->callback += boost::bind(&CList::update, this);
-	scrollUp->callback += boost::bind(&CList::update, this);
+	scrollDown->addCallback(boost::bind(&CList::update, this));
+	scrollUp->addCallback(boost::bind(&CList::update, this));
 
 	update();
 }

+ 3 - 3
client/widgets/AdventureMapClasses.h

@@ -9,7 +9,7 @@ class CGGarrison;
 class CGObjectInstance;
 class CGHeroInstance;
 class CGTownInstance;
-class CAdventureMapButton;
+class CButton;
 struct Component;
 struct InfoAboutArmy;
 struct InfoAboutHero;
@@ -83,8 +83,8 @@ protected:
 
 public:
 
-	CAdventureMapButton * scrollUp;
-	CAdventureMapButton * scrollDown;
+	CButton * scrollUp;
+	CButton * scrollDown;
 
 	/// functions that will be called when selection changes
 	CFunctionList<void()> onSelect;

+ 230 - 241
client/widgets/Buttons.cpp

@@ -24,21 +24,40 @@
  *
  */
 
-CButtonBase::CButtonBase()
+ClickableArea::ClickableArea(CIntObject * object, CFunctionList<void()> callback):
+	callback(callback),
+	area(nullptr)
 {
-	swappedImages = keepFrame = false;
-	bitmapOffset = 0;
-	state=NORMAL;
-	image = nullptr;
-	overlay = nullptr;
+	if (object)
+		pos = object->pos;
+	setArea(object);
+}
+
+void ClickableArea::addCallback(std::function<void()> callback)
+{
+	this->callback += callback;
 }
 
-CButtonBase::~CButtonBase()
+void ClickableArea::setArea(CIntObject * object)
 {
+	delete area;
+	addChild(area);
+	pos.w = object->pos.w;
+	pos.h = object->pos.h;
+}
 
+void ClickableArea::onClick()
+{
+	callback();
 }
 
-void CButtonBase::update()
+void ClickableArea::clickLeft(tribool down, bool previousState)
+{
+	if (down)
+		onClick();
+}
+
+void CButton::update()
 {
 	if (overlay)
 	{
@@ -48,34 +67,31 @@ void CButtonBase::update()
 			overlay->moveTo(overlay->pos.centerIn(pos).topLeft());
 	}
 
-	int newPos = (int)state + bitmapOffset;
+	int newPos = stateToIndex[int(state)];
 	if (newPos < 0)
 		newPos = 0;
 
 	if (state == HIGHLIGHTED && image->size() < 4)
 		newPos = image->size()-1;
-
-	if (swappedImages)
-	{
-		if (newPos == 0) newPos = 1;
-		else if (newPos == 1) newPos = 0;
-	}
-
-	if (!keepFrame)
-		image->setFrame(newPos);
+	image->setFrame(newPos);
 
 	if (active)
 		redraw();
 }
 
-void CButtonBase::addTextOverlay( const std::string &Text, EFonts font, SDL_Color color)
+void CButton::addCallback(std::function<void()> callback)
+{
+	this->callback += callback;
+}
+
+void CButton::addTextOverlay( const std::string &Text, EFonts font, SDL_Color color)
 {
 	OBJ_CONSTRUCTION_CAPTURING_ALL;
 	addOverlay(new CLabel(pos.w/2, pos.h/2, font, CENTER, color, Text));
 	update();
 }
 
-void CButtonBase::addOverlay(CIntObject *newOverlay)
+void CButton::addOverlay(CIntObject *newOverlay)
 {
 	delete overlay;
 	overlay = newOverlay;
@@ -84,15 +100,26 @@ void CButtonBase::addOverlay(CIntObject *newOverlay)
 	update();
 }
 
-void CButtonBase::setOffset(int newOffset)
+void CButton::addImage(std::string filename)
 {
-	if (bitmapOffset == newOffset)
-		return;
-	bitmapOffset = newOffset;
+	imageNames.push_back(filename);
+}
+
+void CButton::addHoverText(ButtonState state, std::string text)
+{
+	hoverTexts[state] = text;
+}
+
+void CButton::setImageOrder(int state1, int state2, int state3, int state4)
+{
+	stateToIndex[0] = state1;
+	stateToIndex[1] = state2;
+	stateToIndex[2] = state3;
+	stateToIndex[3] = state4;
 	update();
 }
 
-void CButtonBase::setState(ButtonState newState)
+void CButton::setState(ButtonState newState)
 {
 	if (state == newState)
 		return;
@@ -100,58 +127,30 @@ void CButtonBase::setState(ButtonState newState)
 	update();
 }
 
-CButtonBase::ButtonState CButtonBase::getState()
+CButton::ButtonState CButton::getState()
 {
 	return state;
 }
 
-bool CButtonBase::isBlocked()
+bool CButton::isBlocked()
 {
 	return state == BLOCKED;
 }
 
-bool CButtonBase::isHighlighted()
+bool CButton::isHighlighted()
 {
 	return state == HIGHLIGHTED;
 }
 
-void CButtonBase::block(bool on)
+void CButton::block(bool on)
 {
 	setState(on?BLOCKED:NORMAL);
 }
 
-CAdventureMapButton::CAdventureMapButton ()
-{
-	hoverable = actOnDown = borderEnabled = soundDisabled = false;
-	CSDL_Ext::colorSetAlpha(borderColor,1);// represents a transparent color, used for HighlightableButton
-	addUsedEvents(LCLICK | RCLICK | HOVER | KEYBOARD);
-}
-
-CAdventureMapButton::CAdventureMapButton( const std::string &Name, const std::string &HelpBox, const CFunctionList<void()> &Callback, int x, int y,  const std::string &defName,int key, std::vector<std::string> * add, bool playerColoredButton )
-{
-	std::map<int,std::string> pom;
-	pom[0] = Name;
-	init(Callback, pom, HelpBox, playerColoredButton, defName, add, x, y, key);
-}
-
-CAdventureMapButton::CAdventureMapButton( const std::string &Name, const std::string &HelpBox, const CFunctionList<void()> &Callback, config::ButtonInfo *info, int key/*=0*/ )
-{
-	std::map<int,std::string> pom;
-	pom[0] = Name;
-	init(Callback, pom, HelpBox, info->playerColoured, info->defName, &info->additionalDefs, info->x, info->y, key);
-}
-
-CAdventureMapButton::CAdventureMapButton( const std::pair<std::string, std::string> &help, const CFunctionList<void()> &Callback, int x, int y, const std::string &defName, int key/*=0*/, std::vector<std::string> * add /*= nullptr*/, bool playerColoredButton /*= false */ )
-{
-	std::map<int,std::string> pom;
-	pom[0] = help.first;
-	init(Callback, pom, help.second, playerColoredButton, defName, add, x, y, key);
-}
-
-void CAdventureMapButton::onButtonClicked()
+void CButton::onButtonClicked()
 {
 	// debug logging to figure out pressed button (and as result - player actions) in case of crash
-	logAnim->traceStream() << "Button clicked at " << pos.x << "x" << pos.y;
+	logAnim->traceStream() << "Button clicked at " << pos.x << "x" << pos.y << ", " << callback.funcs.size() << " functions";
 	CIntObject * parent = this->parent;
 	std::string prefix = "Parent is";
 	while (parent)
@@ -163,7 +162,7 @@ void CAdventureMapButton::onButtonClicked()
 	callback();
 }
 
-void CAdventureMapButton::clickLeft(tribool down, bool previousState)
+void CButton::clickLeft(tribool down, bool previousState)
 {
 	if(isBlocked())
 		return;
@@ -189,13 +188,13 @@ void CAdventureMapButton::clickLeft(tribool down, bool previousState)
 	}
 }
 
-void CAdventureMapButton::clickRight(tribool down, bool previousState)
+void CButton::clickRight(tribool down, bool previousState)
 {
 	if(down && helpBox.size()) //there is no point to show window with nothing inside...
 		CRClickPopup::createAndPush(helpBox);
 }
 
-void CAdventureMapButton::hover (bool on)
+void CButton::hover (bool on)
 {
 	if(hoverable)
 	{
@@ -208,19 +207,20 @@ void CAdventureMapButton::hover (bool on)
 	if(pressedL && on)
 		setState(PRESSED);
 
-	std::string *name = (vstd::contains(hoverTexts,getState()))
-		? (&hoverTexts[getState()])
-		: (vstd::contains(hoverTexts,0) ? (&hoverTexts[0]) : nullptr);
-	if(name && name->size() && !isBlocked()) //if there is no name, there is nohing to display also
+	std::string name = hoverTexts[getState()].empty()
+		? hoverTexts[getState()]
+		: hoverTexts[0];
+
+	if(!name.empty() && !isBlocked()) //if there is no name, there is nohing to display also
 	{
 		if (LOCPLINT && LOCPLINT->battleInt) //for battle buttons
 		{
 			if(on && LOCPLINT->battleInt->console->alterTxt == "")
 			{
-				LOCPLINT->battleInt->console->alterTxt = *name;
+				LOCPLINT->battleInt->console->alterTxt = name;
 				LOCPLINT->battleInt->console->whoSetAlter = 1;
 			}
-			else if (LOCPLINT->battleInt->console->alterTxt == *name)
+			else if (LOCPLINT->battleInt->console->alterTxt == name)
 			{
 				LOCPLINT->battleInt->console->alterTxt = "";
 				LOCPLINT->battleInt->console->whoSetAlter = 0;
@@ -229,38 +229,44 @@ void CAdventureMapButton::hover (bool on)
 		else if(GH.statusbar) //for other buttons
 		{
 			if (on)
-				GH.statusbar->setText(*name);
-			else if ( GH.statusbar->getText()==(*name) )
+				GH.statusbar->setText(name);
+			else if ( GH.statusbar->getText()==(name) )
 				GH.statusbar->clear();
 		}
 	}
 }
 
-void CAdventureMapButton::init(const CFunctionList<void()> &Callback, const std::map<int,std::string> &Name, const std::string &HelpBox, bool playerColoredButton, const std::string &defName, std::vector<std::string> * add, int x, int y, int key)
+CButton::CButton(Point position, const std::string &defName, const std::pair<std::string, std::string> &help, CFunctionList<void()> Callback, int key, bool playerColoredButton):
+    CKeyShortcut(key),
+    callback(Callback)
 {
-	currentImage = -1;
 	addUsedEvents(LCLICK | RCLICK | HOVER | KEYBOARD);
-	callback = Callback;
-	hoverable = actOnDown = borderEnabled = soundDisabled = false;
-	CSDL_Ext::colorSetAlpha(borderColor,1);// represents a transparent color, used for HighlightableButton
-	hoverTexts = Name;
-	helpBox=HelpBox;
 
-	if (key != SDLK_UNKNOWN)
-		assignedKeys.insert(key);
+	stateToIndex[0] = 0;
+	stateToIndex[1] = 1;
+	stateToIndex[2] = 2;
+	stateToIndex[3] = 3;
 
-	pos.x += x;
-	pos.y += y;
+	state=NORMAL;
+	image = nullptr;
+	overlay = nullptr;
+
+	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);
-	if (add)
-		for (auto & elem : *add)
-			imageNames.push_back(elem);
-	setIndex(0, playerColoredButton);
+		setIndex(0, playerColoredButton);
+	}
 }
 
-void CAdventureMapButton::setIndex(size_t index, bool playerColoredButton)
+void CButton::setIndex(size_t index, bool playerColoredButton)
 {
 	if (index == currentImage || index>=imageNames.size())
 		return;
@@ -268,67 +274,117 @@ void CAdventureMapButton::setIndex(size_t index, bool playerColoredButton)
 	setImage(new CAnimation(imageNames[index]), playerColoredButton);
 }
 
-void CAdventureMapButton::setImage(CAnimation* anim, bool playerColoredButton, int animFlags)
+void CButton::setImage(CAnimation* anim, bool playerColoredButton, int animFlags)
 {
 	OBJ_CONSTRUCTION_CAPTURING_ALL;
 
-	delete image;
 	image = new CAnimImage(anim, getState(), 0, 0, 0, animFlags);
 	if (playerColoredButton)
 		image->playerColored(LOCPLINT->playerID);
-
-	pos.w = image->pos.w;
-	pos.h = image->pos.h;
+	pos = image->pos;
 }
 
-void CAdventureMapButton::setPlayerColor(PlayerColor player)
+void CButton::setPlayerColor(PlayerColor player)
 {
 	if (image)
 		image->playerColored(player);
 }
 
-void CAdventureMapButton::showAll(SDL_Surface * to)
+void CButton::showAll(SDL_Surface * to)
 {
 	CIntObject::showAll(to);
 	
 	#ifdef VCMI_SDL1
-	if (borderEnabled && borderColor.unused == 0)
-		CSDL_Ext::drawBorder(to, pos.x-1, pos.y-1, pos.w+2, pos.h+2, int3(borderColor.r, borderColor.g, borderColor.b));	
+	if (borderColor && borderColor->unused == 0)
+		CSDL_Ext::drawBorder(to, pos.x-1, pos.y-1, pos.w+2, pos.h+2, int3(borderColor->r, borderColor->g, borderColor->b));
 	#else
-	if (borderEnabled && borderColor.a == 0)
-		CSDL_Ext::drawBorder(to, pos.x-1, pos.y-1, pos.w+2, pos.h+2, int3(borderColor.r, borderColor.g, borderColor.b));	
+	if (borderColor && borderColor->a == 0)
+		CSDL_Ext::drawBorder(to, pos.x-1, pos.y-1, pos.w+2, pos.h+2, int3(borderColor->r, borderColor->g, borderColor->b));
 	#endif // 0
 }
 
-void CHighlightableButton::select(bool on)
+std::pair<std::string, std::string> CButton::tooltip()
+{
+	return std::pair<std::string, std::string>();
+}
+
+std::pair<std::string, std::string> CButton::tooltip(const JsonNode & localizedTexts)
+{
+	return std::make_pair(localizedTexts["label"].String(), localizedTexts["help"].String());
+}
+
+std::pair<std::string, std::string> CButton::tooltip(const std::string & hover, const std::string & help)
+{
+	return std::make_pair(hover, help);
+}
+
+CToggleBase::CToggleBase(CFunctionList<void (bool)> callback):
+    callback(callback)
+{
+}
+
+CToggleBase::~CToggleBase()
+{
+}
+
+void CToggleBase::doSelect(bool on)
+{
+	// for overrides
+}
+
+void CToggleBase::setSelected(bool on)
 {
 	selected = on;
+	doSelect(on);
+}
+
+void CToggleBase::activate()
+{
+	if (canActivate())
+		setSelected(!selected);
+}
+
+bool CToggleBase::canActivate()
+{
+	if (selected && !allowDeselection)
+		return false;
+	return true;
+}
+
+void CToggleBase::addCallback(std::function<void(bool)> function)
+{
+	callback.add(function);
+}
+
+CToggleButton::CToggleButton(Point position, const std::string &defName, const std::pair<std::string, std::string> &help,
+                             CFunctionList<void(bool)> callback, int key, bool playerColoredButton):
+  CButton(position, defName, help, 0, key, playerColoredButton),
+  CToggleBase(callback)
+{
+}
+
+void CToggleButton::doSelect(bool on)
+{
 	if (on)
 	{
-		borderEnabled = true;
 		setState(HIGHLIGHTED);
-		callback();
 	}
 	else
 	{
-		borderEnabled = false;
 		setState(NORMAL);
-		callback2();
-	}
-
-	if(hoverTexts.size()>1)
-	{
-		hover(false);
-		hover(true);
 	}
 }
 
-void CHighlightableButton::clickLeft(tribool down, bool previousState)
+void CToggleButton::clickLeft(tribool down, bool previousState)
 {
+	// force refresh
+	hover(false);
+	hover(true);
+
 	if(isBlocked())
 		return;
 
-	if (down && !(onlyOn && isHighlighted()))
+	if (down && canActivate())
 	{
 		CCS->soundh->playSound(soundBase::button);
 		setState(PRESSED);
@@ -337,132 +393,85 @@ void CHighlightableButton::clickLeft(tribool down, bool previousState)
 	if(previousState)//mouse up
 	{
 		if(down == false && getState() == PRESSED)
-			select(!selected);
+			setSelected(!selected);
 		else
-			setState(selected?HIGHLIGHTED:NORMAL);
+			setSelected(selected);
 	}
 }
 
-CHighlightableButton::CHighlightableButton( const CFunctionList<void()> &onSelect, const CFunctionList<void()> &onDeselect, const std::map<int,std::string> &Name, const std::string &HelpBox, bool playerColoredButton, const std::string &defName, std::vector<std::string> * add, int x, int y, int key)
-: onlyOn(false), selected(false), callback2(onDeselect)
-{
-	init(onSelect,Name,HelpBox,playerColoredButton,defName,add,x,y,key);
-}
-
-CHighlightableButton::CHighlightableButton( const std::pair<std::string, std::string> &help, const CFunctionList<void()> &onSelect, int x, int y, const std::string &defName, int myid, int key/*=0*/, std::vector<std::string> * add /*= nullptr*/, bool playerColoredButton /*= false */ )
-: onlyOn(false), selected(false) // TODO: callback2(???)
+void CToggleGroup::addCallback(std::function<void(int)> callback)
 {
-	ID = myid;
-	std::map<int,std::string> pom;
-	pom[0] = help.first;
-	init(onSelect, pom, help.second, playerColoredButton, defName, add, x, y, key);
+	onChange += callback;
 }
 
-CHighlightableButton::CHighlightableButton( const std::string &Name, const std::string &HelpBox, const CFunctionList<void()> &onSelect, int x, int y, const std::string &defName, int myid, int key/*=0*/, std::vector<std::string> * add /*= nullptr*/, bool playerColoredButton /*= false */ )
-: onlyOn(false), selected(false) // TODO: callback2(???)
+void CToggleGroup::addToggle(int identifier, CToggleBase* bt)
 {
-	ID = myid;
-	std::map<int,std::string> pom;
-	pom[0] = Name;
-	init(onSelect, pom,HelpBox, playerColoredButton, defName, add, x, y, key);
-}
-
-void CHighlightableButtonsGroup::addButton(CHighlightableButton* bt)
-{
-	if (bt->parent)
-		bt->parent->removeChild(bt);
-	addChild(bt);
-	bt->recActions = defActions;//FIXME: not needed?
-
-	bt->callback += boost::bind(&CHighlightableButtonsGroup::selectionChanged,this,bt->ID);
-	bt->onlyOn = true;
-	buttons.push_back(bt);
-}
-
-void CHighlightableButtonsGroup::addButton(const std::map<int,std::string> &tooltip, const std::string &HelpBox, const std::string &defName, int x, int y, int uid, const CFunctionList<void()> &OnSelect, int key)
-{
-	OBJ_CONSTRUCTION_CAPTURING_ALL;
-	CHighlightableButton *bt = new CHighlightableButton(OnSelect, 0, tooltip, HelpBox, false, defName, nullptr, x, y, key);
-	if(musicLike)
+	if (auto intObj = dynamic_cast<CIntObject*>(bt)) // hack-ish workagound to avoid diamond problem with inheritance
 	{
-		bt->setOffset(buttons.size()-3);
+		if (intObj->parent)
+			intObj->parent->removeChild(intObj);
+		addChild(intObj);
 	}
-	bt->ID = uid;
-	bt->callback += boost::bind(&CHighlightableButtonsGroup::selectionChanged,this,bt->ID);
-	bt->onlyOn = true;
-	buttons.push_back(bt);
+
+	bt->addCallback(boost::bind(&CToggleGroup::selectionChanged, this, identifier));
+	bt->allowDeselection = false;
+
+	assert(!buttons.count(identifier));
+	buttons[identifier] = bt;
 }
 
-CHighlightableButtonsGroup::CHighlightableButtonsGroup(const CFunctionList<void(int)> &OnChange, bool musicLikeButtons)
+CToggleGroup::CToggleGroup(const CFunctionList<void(int)> &OnChange, bool musicLikeButtons)
 : onChange(OnChange), musicLike(musicLikeButtons)
 {}
 
-CHighlightableButtonsGroup::~CHighlightableButtonsGroup()
-{
-
-}
-
-void CHighlightableButtonsGroup::select(int id, bool mode)
+void CToggleGroup::setSelected(int id)
 {
 	assert(!buttons.empty());
 
-	CHighlightableButton *bt = buttons.front();
-	if(mode)
+	auto bt = buttons[id];
+	if (bt)
 	{
-		for(auto btn : buttons)
-			if (btn->ID == id)
-				bt = btn;
+		bt->setSelected(true);
+		selectionChanged(id);
 	}
-	else
-	{
-		bt = buttons[id];
-	}
-	bt->select(true);
-	selectionChanged(bt->ID);
 }
 
-void CHighlightableButtonsGroup::selectionChanged(int to)
+void CToggleGroup::selectionChanged(int to)
 {
-	for(auto & elem : buttons)
-		if(elem->ID!=to && elem->isHighlighted())
-			elem->select(false);
+	if (buttons.count(selectedID))
+		buttons[selectedID]->setSelected(false);
+
+	if (buttons.count(to))
+		buttons[to]->setSelected(true);
+
+	selectedID = to;
 	onChange(to);
 	if (parent)
 		parent->redraw();
 }
 
-void CHighlightableButtonsGroup::show(SDL_Surface * to)
+void CToggleGroup::show(SDL_Surface * to)
 {
 	if (musicLike)
 	{
-		for(auto & elem : buttons)
-			if(elem->isHighlighted())
-				elem->show(to);
+		if (auto intObj = dynamic_cast<CIntObject*>(buttons[selectedID])) // hack-ish workagound to avoid diamond problem with inheritance
+			intObj->show(to);
 	}
 	else
 		CIntObject::show(to);
 }
 
-void CHighlightableButtonsGroup::showAll(SDL_Surface * to)
+void CToggleGroup::showAll(SDL_Surface * to)
 {
 	if (musicLike)
 	{
-		for(auto & elem : buttons)
-			if(elem->isHighlighted())
-				elem->showAll(to);
+		if (auto intObj = dynamic_cast<CIntObject*>(buttons[selectedID])) // hack-ish workagound to avoid diamond problem with inheritance
+			intObj->showAll(to);
 	}
 	else
 		CIntObject::showAll(to);
 }
 
-void CHighlightableButtonsGroup::block( ui8 on )
-{
-	for(auto & elem : buttons)
-	{
-		elem->block(on);
-	}
-}
-
 void CSlider::sliderClicked()
 {
 	if(!(active & MOVE))
@@ -596,73 +605,53 @@ CSlider::CSlider(int x, int y, int totalw, std::function<void(int)> Moved, int C
 	addUsedEvents(LCLICK | KEYBOARD | WHEEL);
 	strongInterest = true;
 
-
-	left = new CAdventureMapButton();
-	right = new CAdventureMapButton();
-	slider = new CAdventureMapButton();
-
 	pos.x += x;
 	pos.y += y;
 
-	if(horizontal)
-	{
-		left->pos.y = slider->pos.y = right->pos.y = pos.y;
-		left->pos.x = pos.x;
-		right->pos.x = pos.x + totalw - 16;
-	}
-	else
-	{
-		left->pos.x = slider->pos.x = right->pos.x = pos.x;
-		left->pos.y = pos.y;
-		right->pos.y = pos.y + totalw - 16;
-	}
-
-	left->callback = boost::bind(&CSlider::moveLeft,this);
-	right->callback = boost::bind(&CSlider::moveRight,this);
-	slider->callback = boost::bind(&CSlider::sliderClicked,this);
-	left->pos.w = left->pos.h = right->pos.w = right->pos.h = slider->pos.w = slider->pos.h = 16;
-	if(horizontal)
-	{
-		pos.h = 16;
-		pos.w = totalw;
-	}
-	else
-	{
-		pos.w = 16;
-		pos.h = totalw;
-	}
-
 	if(style == 0)
 	{
 		std::string name = horizontal?"IGPCRDIV.DEF":"OVBUTN2.DEF";
 		//NOTE: this images do not have "blocked" frames. They should be implemented somehow (e.g. palette transform or something...)
 
-		//use source def to create custom animations. Format "name.def:123" will load this frame from def file
-		auto animLeft = new CAnimation();
-		animLeft->setCustom(name + ":0", 0);
-		animLeft->setCustom(name + ":1", 1);
-		left->setImage(animLeft);
+		left =   new CButton(Point(), name, CButton::tooltip());
+		right =  new CButton(Point(), name, CButton::tooltip());
+		slider = new CButton(Point(), name, CButton::tooltip());
 
-		auto animRight = new CAnimation();
-		animRight->setCustom(name + ":2", 0);
-		animRight->setCustom(name + ":3", 1);
-		right->setImage(animRight);
-
-		auto animSlider = new CAnimation();
-		animSlider->setCustom(name + ":4", 0);
-		slider->setImage(animSlider);
+		left->setImageOrder(0, 1, 1, 1);
+		right->setImageOrder(2, 3, 3, 3);
+		slider->setImageOrder(4, 4, 4, 4);
 	}
 	else
 	{
-		left->setImage(new CAnimation(horizontal ? "SCNRBLF.DEF" : "SCNRBUP.DEF"));
-		right->setImage(new CAnimation(horizontal ? "SCNRBRT.DEF" : "SCNRBDN.DEF"));
-		slider->setImage(new CAnimation("SCNRBSL.DEF"));
+		left = new CButton(Point(), horizontal ? "SCNRBLF.DEF" : "SCNRBUP.DEF", CButton::tooltip());
+		right = new CButton(Point(), horizontal ? "SCNRBRT.DEF" : "SCNRBDN.DEF", CButton::tooltip());
+		slider = new CButton(Point(), "SCNRBSL.DEF", CButton::tooltip());
 	}
 	slider->actOnDown = true;
 	slider->soundDisabled = true;
 	left->soundDisabled = true;
 	right->soundDisabled = true;
 
+	if (horizontal)
+		right->moveBy(Point(totalw - right->pos.w, 0));
+	else
+		right->moveBy(Point(0, totalw - right->pos.h));
+
+	left->addCallback(boost::bind(&CSlider::moveLeft,this));
+	right->addCallback(boost::bind(&CSlider::moveRight,this));
+	slider->addCallback(boost::bind(&CSlider::sliderClicked,this));
+
+	if(horizontal)
+	{
+		pos.h = slider->pos.h;
+		pos.w = totalw;
+	}
+	else
+	{
+		pos.w = slider->pos.w;
+		pos.h = totalw;
+	}
+
 	value = -1;
 	moveTo(Value);
 }

+ 111 - 66
client/widgets/Buttons.h

@@ -27,9 +27,30 @@ namespace config
  *
  */
 
-/// Base class for buttons.
-class CButtonBase : public CKeyShortcut
+class ClickableArea : public CIntObject //TODO: derive from LRCLickableArea? Or somehow use its right-click/hover data?
 {
+	CFunctionList<void()> callback;
+
+	CIntObject * area;
+
+protected:
+	void onClick();
+
+public:
+	ClickableArea(CIntObject * object, CFunctionList<void()> callback);
+
+	void addCallback(std::function<void()> callback);
+	void setArea(CIntObject * object);
+
+	void clickLeft(tribool down, bool previousState) override;
+};
+
+/// Typical Heroes 3 button which can be inactive or active and can
+/// hold further information if you right-click it
+class CButton : public CKeyShortcut
+{
+	CFunctionList<void()> callback;
+
 public:
 	enum ButtonState
 	{
@@ -39,110 +60,134 @@ public:
 		HIGHLIGHTED=3
 	};
 private:
-	int bitmapOffset; // base offset of visible bitmap from animation
+	std::vector<std::string> imageNames;//store list of images that can be used by this button
+	size_t currentImage;
 	ButtonState state;//current state of button from enum
 
-public:
-	bool swappedImages,//fix for some buttons: normal and pressed image are swapped
-		keepFrame; // don't change visual representation
-
-	void addOverlay(CIntObject * newOverlay);
-	void addTextOverlay(const std::string &Text, EFonts font, SDL_Color color = Colors::WHITE);
+	std::array<int, 4> stateToIndex; // mapping of button state to index of frame in animation
+	std::array<std::string, 4> hoverTexts; //text for statusbar
+	std::string helpBox; //for right-click help
 
+	CAnimImage * image; //image for this button
+	CIntObject * overlay;//object-overlay
+protected:
+	void onButtonClicked(); // calls callback
 	void update();//to refresh button after image or text change
 
-	void setOffset(int newOffset);
 	void setState(ButtonState newState);
 	ButtonState getState();
 
-	//just to make code clearer
+public:
+	bool actOnDown,//runs when mouse is pressed down over it, not when up
+		hoverable,//if true, button will be highlighted when hovered
+		soundDisabled;
+
+	boost::optional<SDL_Color> borderColor;
+
+	void addCallback(std::function<void()> callback);
+	void addOverlay(CIntObject * newOverlay);
+	void addTextOverlay(const std::string &Text, EFonts font, SDL_Color color = Colors::WHITE);
+
+	void addImage(std::string filename);
+	void addHoverText(ButtonState state, std::string text);
+
+	void setImageOrder(int state1, int state2, int state3, int state4);
 	void block(bool on);
+
+	/// State modifiers
 	bool isBlocked();
 	bool isHighlighted();
 
-	CAnimImage * image; //image for this button
-	CIntObject * overlay;//object-overlay
+	/// Constructor
+	CButton(Point position, const std::string &defName, const std::pair<std::string, std::string> &help,
+	        CFunctionList<void()> Callback = 0, int key=0, bool playerColoredButton = false );
+
+	/// Appearance modifiers
+	void setIndex(size_t index, bool playerColoredButton=false);
+	void setImage(CAnimation* anim, bool playerColoredButton=false, int animFlags=0);
+	void setPlayerColor(PlayerColor player);
 
-	CButtonBase(); //c-tor
-	virtual ~CButtonBase(); //d-tor
+	/// CIntObject overrides
+	void clickRight(tribool down, bool previousState) override;
+	void clickLeft(tribool down, bool previousState) override;
+	void hover (bool on) override;
+	void showAll(SDL_Surface * to) override;
+
+	static std::pair<std::string, std::string> tooltip();
+	static std::pair<std::string, std::string> tooltip(const JsonNode & localizedTexts);
+	static std::pair<std::string, std::string> tooltip(const std::string & hover, const std::string & help = "");
 };
 
-/// Typical Heroes 3 button which can be inactive or active and can 
-/// hold further information if you right-click it
-class CAdventureMapButton : public CButtonBase
+class CToggleBase
 {
-	std::vector<std::string> imageNames;//store list of images that can be used by this button
-	size_t currentImage;
+	CFunctionList<void(bool)> callback;
+protected:
 
-	void onButtonClicked(); // calls callback
-public:
-	std::map<int, std::string> hoverTexts; //text for statusbar
-	std::string helpBox; //for right-click help
-	CFunctionList<void()> callback;
-	bool actOnDown,//runs when mouse is pressed down over it, not when up
-		hoverable,//if true, button will be highlighted when hovered
-		borderEnabled,
-		soundDisabled;
-	SDL_Color borderColor;
+	bool selected;
+
+	virtual void doSelect(bool on);
 
-	void clickRight(tribool down, bool previousState);
-	virtual void clickLeft(tribool down, bool previousState);
-	void hover (bool on);
+	// returns true if toggle can change its state
+	bool canActivate();
 
-	CAdventureMapButton(); //c-tor
-	CAdventureMapButton( const std::string &Name, const std::string &HelpBox, const CFunctionList<void()> &Callback, int x, int y, const std::string &defName, int key=0, std::vector<std::string> * add = nullptr, bool playerColoredButton = false );//c-tor
-	CAdventureMapButton( const std::pair<std::string, std::string> &help, const CFunctionList<void()> &Callback, int x, int y, const std::string &defName, int key=0, std::vector<std::string> * add = nullptr, bool playerColoredButton = false );//c-tor
-	CAdventureMapButton( const std::string &Name, const std::string &HelpBox, const CFunctionList<void()> &Callback, config::ButtonInfo *info, int key=0);//c-tor
+public:
+	bool allowDeselection;
 
-	void init(const CFunctionList<void()> &Callback, const std::map<int,std::string> &Name, const std::string &HelpBox, bool playerColoredButton, const std::string &defName, std::vector<std::string> * add, int x, int y, int key );
+	CToggleBase(CFunctionList<void(bool)> callback);
+	virtual ~CToggleBase();
 
-	void setIndex(size_t index, bool playerColoredButton=false);
-	void setImage(CAnimation* anim, bool playerColoredButton=false, int animFlags=0);
-	void setPlayerColor(PlayerColor player);
-	void showAll(SDL_Surface * to);
+	void activate();
+	void setSelected(bool on);
+
+	void addCallback(std::function<void(bool)> callback);
 };
 
-/// A button which can be selected/deselected
-class CHighlightableButton 
-	: public CAdventureMapButton
+class ClickableToggle : public ClickableArea, public CToggleBase
 {
 public:
-	CHighlightableButton(const CFunctionList<void()> &onSelect, const CFunctionList<void()> &onDeselect, const std::map<int,std::string> &Name, const std::string &HelpBox, bool playerColoredButton, const std::string &defName, std::vector<std::string> * add, int x, int y, int key=0);
-	CHighlightableButton(const std::pair<std::string, std::string> &help, const CFunctionList<void()> &onSelect, int x, int y, const std::string &defName, int myid, int key=0, std::vector<std::string> * add = nullptr, bool playerColoredButton = false );//c-tor
-	CHighlightableButton(const std::string &Name, const std::string &HelpBox, const CFunctionList<void()> &onSelect, int x, int y, const std::string &defName, int myid, int key=0, std::vector<std::string> * add = nullptr, bool playerColoredButton = false );//c-tor
-	bool onlyOn;//button can not be de-selected
-	bool selected;//state of highlightable button
-	int ID; //for identification
-	CFunctionList<void()> callback2; //when de-selecting
-	void select(bool on);
-	void clickLeft(tribool down, bool previousState);
+	ClickableToggle(CIntObject * object, CFunctionList<void()> selectFun, CFunctionList<void()> deselectFun);
+	void clickLeft(tribool down, bool previousState) override;
 };
 
-/// A group of buttons where one button can be selected
-class CHighlightableButtonsGroup : public CIntObject
+/// A button which can be selected/deselected, checkbox
+class CToggleButton : public CButton, public CToggleBase
 {
+	void doSelect(bool on) override;
 public:
+	CToggleButton(Point position, const std::string &defName, const std::pair<std::string, std::string> &help,
+	              CFunctionList<void(bool)> Callback = 0, int key=0, bool playerColoredButton = false );
+	void clickLeft(tribool down, bool previousState) override;
+
+	// bring overrides into scope
+	using CButton::addCallback;
+	using CToggleBase::addCallback;
+};
+
+class CToggleGroup : public CIntObject
+{
 	CFunctionList<void(int)> onChange; //called when changing selected button with new button's id
-	std::vector<CHighlightableButton*> buttons;
-	bool musicLike; //determines the behaviour of this group
 
-	//void addButton(const std::map<int,std::string> &tooltip, const std::string &HelpBox, const std::string &defName, int x, int y, int uid);
-	void addButton(CHighlightableButton* bt);//add existing button, it'll be deleted by CHighlightableButtonsGroup destructor
-	void addButton(const std::map<int,std::string> &tooltip, const std::string &HelpBox, const std::string &defName, int x, int y, int uid, const CFunctionList<void()> &OnSelect=0, int key=0); //creates new button
-	CHighlightableButtonsGroup(const CFunctionList<void(int)> & OnChange, bool musicLikeButtons = false);
-	~CHighlightableButtonsGroup();
-	void select(int id, bool mode); //mode==0: id is serial; mode==1: id is unique button id
+	int selectedID;
+	bool musicLike; //determines the behaviour of this group
 	void selectionChanged(int to);
+public:
+	std::map<int, CToggleBase*> buttons;
+
+	CToggleGroup(const CFunctionList<void(int)> & OnChange, bool musicLikeButtons = false);
+
+	void addCallback(std::function<void(int)> callback);
+	void addToggle(int index, CToggleBase * button);
+	void setSelected(int id);
+
 	void show(SDL_Surface * to);
 	void showAll(SDL_Surface * to);
-	void block(ui8 on);
 };
 
 /// A typical slider which can be orientated horizontally/vertically.
 class CSlider : public CIntObject
 {
 public:
-	CAdventureMapButton *left, *right, *slider; //if vertical then left=up
+	CButton *left, *right, *slider; //if vertical then left=up
 	int capacity;//how many elements can be active at same time (e.g. hero list = 5)
 	int amount; //total amount of elements (e.g. hero list = 0-8)
 	int positions; //number of highest position (0 if there is only one)

+ 5 - 5
client/widgets/CArtifactHolder.cpp

@@ -657,7 +657,7 @@ void CArtifactsOfHero::eraseSlotData (CArtPlace* artPlace, ArtifactPosition slot
 }
 
 CArtifactsOfHero::CArtifactsOfHero(std::vector<CArtPlace *> ArtWorn, std::vector<CArtPlace *> Backpack,
-	CAdventureMapButton *leftScroll, CAdventureMapButton *rightScroll, bool createCommonPart):
+	CButton *leftScroll, CButton *rightScroll, bool createCommonPart):
 
 	curHero(nullptr),
 	artWorn(ArtWorn), backpack(Backpack),
@@ -685,8 +685,8 @@ CArtifactsOfHero::CArtifactsOfHero(std::vector<CArtPlace *> ArtWorn, std::vector
 		eraseSlotData(backpack[s], ArtifactPosition(GameConstants::BACKPACK_START + s));
 	}
 
-	leftArtRoll->callback  += boost::bind(&CArtifactsOfHero::scrollBackpack,this,-1);
-	rightArtRoll->callback += boost::bind(&CArtifactsOfHero::scrollBackpack,this,+1);
+	leftArtRoll->addCallback(boost::bind(&CArtifactsOfHero::scrollBackpack,this,-1));
+	rightArtRoll->addCallback(boost::bind(&CArtifactsOfHero::scrollBackpack,this,+1));
 }
 
 CArtifactsOfHero::CArtifactsOfHero(const Point& position, bool createCommonPart /*= false*/)
@@ -732,8 +732,8 @@ CArtifactsOfHero::CArtifactsOfHero(const Point& position, bool createCommonPart
 		backpack.push_back(add);
 	}
 
-	leftArtRoll = new CAdventureMapButton(std::string(), std::string(), boost::bind(&CArtifactsOfHero::scrollBackpack,this,-1), 379, 364, "hsbtns3.def", SDLK_LEFT);
-	rightArtRoll = new CAdventureMapButton(std::string(), std::string(), boost::bind(&CArtifactsOfHero::scrollBackpack,this,+1), 632, 364, "hsbtns5.def", SDLK_RIGHT);
+	leftArtRoll =  new CButton(Point(379, 364), "hsbtns3.def", CButton::tooltip(), [&]{ scrollBackpack(-1);}, SDLK_LEFT);
+	rightArtRoll = new CButton(Point(632, 364), "hsbtns5.def", CButton::tooltip(), [&]{ scrollBackpack(+1);}, SDLK_RIGHT);
 }
 
 CArtifactsOfHero::~CArtifactsOfHero()

+ 3 - 3
client/widgets/CArtifactHolder.h

@@ -15,7 +15,7 @@
 
 class CArtifactsOfHero;
 class CAnimImage;
-class CAdventureMapButton;
+class CButton;
 
 struct ArtifactLocation;
 
@@ -108,7 +108,7 @@ public:
 
 	bool updateState; // Whether the commonInfo should be updated on setHero or not.
 
-	CAdventureMapButton * leftArtRoll, * rightArtRoll;
+	CButton * leftArtRoll, * rightArtRoll;
 	bool allowedAssembling;
 	std::multiset<const CArtifactInstance*> artifactsOnAltar; //artifacts id that are technically present in backpack but in GUI are moved to the altar - they'll be omitted in backpack slots
 	std::function<void(CArtPlace*)> highlightModeCallback; //if set, clicking on art place doesn't pick artifact but highlights the slot and calls this function
@@ -138,7 +138,7 @@ public:
 	CArtifactsOfHero(const Point& position, bool createCommonPart = false);
 	//Alternative constructor, used if custom artifacts positioning required (Kingdom interface)
 	CArtifactsOfHero(std::vector<CArtPlace *> ArtWorn, std::vector<CArtPlace *> Backpack,
-		CAdventureMapButton *leftScroll, CAdventureMapButton *rightScroll, bool createCommonPart = false);
+		CButton *leftScroll, CButton *rightScroll, bool createCommonPart = false);
 	~CArtifactsOfHero(); //d-tor
 	void updateParentWindow();
 	friend class CArtPlace;

+ 3 - 3
client/widgets/CGarrisonInt.cpp

@@ -339,7 +339,7 @@ CGarrisonSlot::CGarrisonSlot(CGarrisonInt *Owner, int x, int y, SlotID IID, int
 		stackCount->setText(boost::lexical_cast<std::string>(myStack->count));
 }
 
-void CGarrisonInt::addSplitBtn(CAdventureMapButton * button)
+void CGarrisonInt::addSplitBtn(CButton * button)
 {
 	addChild(button);
 	button->recActions = defActions;
@@ -482,11 +482,11 @@ CGarrisonWindow::CGarrisonWindow( const CArmedInstance *up, const CGHeroInstance
 
 	garr = new CGarrisonInt(92, 127, 4, Point(0,96), background->bg, Point(93,127), up, down, removableUnits);
 	{
-		CAdventureMapButton *split = new CAdventureMapButton(CGI->generaltexth->tcommands[3],"",boost::bind(&CGarrisonInt::splitClick,garr),88,314,"IDV6432.DEF");
+		CButton *split = new CButton(Point(88, 314), "IDV6432.DEF", CButton::tooltip(CGI->generaltexth->tcommands[3], ""), [&]{ garr->splitClick(); } );
 		removeChild(split);
 		garr->addSplitBtn(split);
 	}
-	quit = new CAdventureMapButton(CGI->generaltexth->tcommands[8],"",boost::bind(&CGarrisonWindow::close,this),399,314,"IOK6432.DEF",SDLK_RETURN);
+	quit = new CButton(Point(399, 314), "IOK6432.DEF", CButton::tooltip(CGI->generaltexth->tcommands[8], ""), [&]{ close(); }, SDLK_RETURN);
 
 	std::string titleText;
 	if (garr->armedObjs[1]->tempOwner == garr->armedObjs[0]->tempOwner)

+ 4 - 4
client/widgets/CGarrisonInt.h

@@ -13,7 +13,7 @@
  */
 
 class CGarrisonInt;
-class CAdventureMapButton;
+class CButton;
 class CArmedInstance;
 class CAnimImage;
 class CCreatureSet;
@@ -62,7 +62,7 @@ public:
 
 	int interx; //space between slots
 	Point garOffset; //offset between garrisons (not used if only one hero)
-	std::vector<CAdventureMapButton *> splitButtons; //may be empty if no buttons
+	std::vector<CButton *> splitButtons; //may be empty if no buttons
 
 	SlotID p2; //TODO: comment me
 	int	shiftPos;//1st slot of the second row, set shiftPoint for effect
@@ -80,7 +80,7 @@ public:
 	//const CArmedInstance *oup, *odown; //upper and lower garrisons (heroes or towns)
 
 	void setArmy(const CArmedInstance *army, bool bottomGarrison);
-	void addSplitBtn(CAdventureMapButton * button);
+	void addSplitBtn(CButton * button);
 	void createSet(std::vector<CGarrisonSlot*> &ret, const CCreatureSet * set, int posX, int distance, int posY, int Upg );
 
 	void createSlots();
@@ -116,7 +116,7 @@ public:
 class CGarrisonWindow : public CWindowObject, public CWindowWithGarrison
 {
 public:
-	CAdventureMapButton * quit;
+	CButton * quit;
 
 	CGarrisonWindow(const CArmedInstance *up, const CGHeroInstance *down, bool removableUnits); //c-tor
 };

+ 83 - 93
client/windows/CAdvmapInterface.cpp

@@ -370,35 +370,6 @@ void CResDataBar::showAll(SDL_Surface * to)
 CAdvMapInt::CAdvMapInt():
     minimap(Rect(ADVOPT.minimapX, ADVOPT.minimapY, ADVOPT.minimapW, ADVOPT.minimapH)),
 statusbar(ADVOPT.statusbarX,ADVOPT.statusbarY,ADVOPT.statusbarG),
-kingOverview(CGI->generaltexth->zelp[293].first,CGI->generaltexth->zelp[293].second,
-			 boost::bind(&CAdvMapInt::fshowOverview,this),&ADVOPT.kingOverview, SDLK_k),
-
-underground(CGI->generaltexth->zelp[294].first,CGI->generaltexth->zelp[294].second,
-			boost::bind(&CAdvMapInt::fswitchLevel,this),&ADVOPT.underground, SDLK_u),
-
-questlog(CGI->generaltexth->zelp[295].first,CGI->generaltexth->zelp[295].second,
-		 boost::bind(&CAdvMapInt::fshowQuestlog,this),&ADVOPT.questlog, SDLK_q),
-
-sleepWake(CGI->generaltexth->zelp[296].first,CGI->generaltexth->zelp[296].second,
-		  boost::bind(&CAdvMapInt::fsleepWake,this), &ADVOPT.sleepWake, SDLK_w),
-
-moveHero(CGI->generaltexth->zelp[297].first,CGI->generaltexth->zelp[297].second,
-		  boost::bind(&CAdvMapInt::fmoveHero,this), &ADVOPT.moveHero, SDLK_m),
-
-spellbook(CGI->generaltexth->zelp[298].first,CGI->generaltexth->zelp[298].second,
-		  boost::bind(&CAdvMapInt::fshowSpellbok,this), &ADVOPT.spellbook, SDLK_c),
-
-advOptions(CGI->generaltexth->zelp[299].first,CGI->generaltexth->zelp[299].second,
-		  boost::bind(&CAdvMapInt::fadventureOPtions,this), &ADVOPT.advOptions, SDLK_a),
-
-sysOptions(CGI->generaltexth->zelp[300].first,CGI->generaltexth->zelp[300].second,
-		  boost::bind(&CAdvMapInt::fsystemOptions,this), &ADVOPT.sysOptions, SDLK_o),
-
-nextHero(CGI->generaltexth->zelp[301].first,CGI->generaltexth->zelp[301].second,
-		  boost::bind(&CAdvMapInt::fnextHero,this), &ADVOPT.nextHero, SDLK_h),
-
-endTurn(CGI->generaltexth->zelp[302].first,CGI->generaltexth->zelp[302].second,
-		  boost::bind(&CAdvMapInt::fendTurn,this), &ADVOPT.endTurn, SDLK_e),
 
 heroList(ADVOPT.hlistSize, Point(ADVOPT.hlistX, ADVOPT.hlistY), ADVOPT.hlistAU, ADVOPT.hlistAD),
 townList(ADVOPT.tlistSize, Point(ADVOPT.tlistX, ADVOPT.tlistY), ADVOPT.tlistAU, ADVOPT.tlistAD),
@@ -427,9 +398,28 @@ infoBar(Rect(ADVOPT.infoboxX, ADVOPT.infoboxY, 192, 192) )
 		gems.push_back(CDefHandler::giveDef(ADVOPT.gemG[g]));
 	}
 
+	auto makeButton = [&] (int textID, std::function<void()> callback, config::ButtonInfo info, int key) -> CButton *
+	{
+		auto button = new CButton(Point(info.x, info.y), info.defName, CGI->generaltexth->zelp[textID], callback, key, info.playerColoured);
+
+		for (auto image : info.additionalDefs)
+			button->addImage(image);
+		return button;
+	};
+
+	kingOverview = makeButton(293, boost::bind(&CAdvMapInt::fshowOverview,this),     ADVOPT.kingOverview, SDLK_k);
+	underground  = makeButton(294, boost::bind(&CAdvMapInt::fswitchLevel,this),      ADVOPT.underground,  SDLK_u);
+	questlog     = makeButton(295, boost::bind(&CAdvMapInt::fshowQuestlog,this),     ADVOPT.questlog,     SDLK_q);
+	sleepWake    = makeButton(296, boost::bind(&CAdvMapInt::fsleepWake,this),        ADVOPT.sleepWake,    SDLK_w);
+	moveHero     = makeButton(297, boost::bind(&CAdvMapInt::fmoveHero,this),         ADVOPT.moveHero,     SDLK_m);
+	spellbook    = makeButton(298, boost::bind(&CAdvMapInt::fshowSpellbok,this),     ADVOPT.spellbook,    SDLK_c);
+	advOptions   = makeButton(299, boost::bind(&CAdvMapInt::fadventureOPtions,this), ADVOPT.advOptions,   SDLK_a);
+	sysOptions   = makeButton(300, boost::bind(&CAdvMapInt::fsystemOptions,this),    ADVOPT.sysOptions,   SDLK_o);
+	nextHero     = makeButton(301, boost::bind(&CAdvMapInt::fnextHero,this),         ADVOPT.nextHero,     SDLK_h);
+	endTurn      = makeButton(302, boost::bind(&CAdvMapInt::fendTurn,this),          ADVOPT.endTurn,      SDLK_e);
 
 	setPlayer(LOCPLINT->playerID);
-	underground.block(!CGI->mh->map->twoLevel);
+	underground->block(!CGI->mh->map->twoLevel);
 	addUsedEvents(MOVE);
 }
 
@@ -452,14 +442,14 @@ void CAdvMapInt::fswitchLevel()
 	if (position.z)
 	{
 		position.z--;
-		underground.setIndex(0,true);
-		underground.showAll(screenBuf);
+		underground->setIndex(0,true);
+		underground->showAll(screenBuf);
 	}
 	else
 	{
-		underground.setIndex(1,true);
+		underground->setIndex(1,true);
 		position.z++;
-		underground.showAll(screenBuf);
+		underground->showAll(screenBuf);
 	}
 	updateScreen = true;
 	minimap.setLevel(position.z);
@@ -544,14 +534,13 @@ void CAdvMapInt::fendTurn()
 
 void CAdvMapInt::updateSleepWake(const CGHeroInstance *h)
 {
-	sleepWake.block(!h);
+	sleepWake->block(!h);
 	if (!h)
 		return;
 	bool state = isHeroSleeping(h);
-	sleepWake.setIndex(state ? 1 : 0, true);
-	sleepWake.assignedKeys.clear();
-	sleepWake.assignedKeys.insert(state ? SDLK_w : SDLK_z);
-	sleepWake.update();
+	sleepWake->setIndex(state ? 1 : 0, true);
+	sleepWake->assignedKeys.clear();
+	sleepWake->assignedKeys.insert(state ? SDLK_w : SDLK_z);
 }
 
 void CAdvMapInt::updateMoveHero(const CGHeroInstance *h, tribool hasPath)
@@ -561,10 +550,10 @@ void CAdvMapInt::updateMoveHero(const CGHeroInstance *h, tribool hasPath)
 		 hasPath = LOCPLINT->paths[h].nodes.size() ? true : false;
 	if (!h)
 	{
-		moveHero.block(true);
+		moveHero->block(true);
 		return;
 	}
-	moveHero.block(!hasPath || (h->movement == 0));
+	moveHero->block(!hasPath || (h->movement == 0));
 }
 
 int CAdvMapInt::getNextHeroIndex(int startIndex)
@@ -594,12 +583,12 @@ void CAdvMapInt::updateNextHero(const CGHeroInstance *h)
 	int next = getNextHeroIndex(start);
 	if (next < 0)
 	{
-		nextHero.block(true);
+		nextHero->block(true);
 		return;
 	}
 	const CGHeroInstance *nextH = LOCPLINT->wanderingHeroes[next];
 	bool noActiveHeroes = (next == start) && ((nextH->movement == 0) || isHeroSleeping(nextH));
-	nextHero.block(noActiveHeroes);
+	nextHero->block(noActiveHeroes);
 }
 
 void CAdvMapInt::activate()
@@ -612,16 +601,16 @@ void CAdvMapInt::activate()
 	GH.statusbar = &statusbar;
 	if(!duringAITurn)
 	{
-		kingOverview.activate();
-		underground.activate();
-		questlog.activate();
-		sleepWake.activate();
-		moveHero.activate();
-		spellbook.activate();
-		sysOptions.activate();
-		advOptions.activate();
-		nextHero.activate();
-		endTurn.activate();
+		kingOverview->activate();
+		underground->activate();
+		questlog->activate();
+		sleepWake->activate();
+		moveHero->activate();
+		spellbook->activate();
+		sysOptions->activate();
+		advOptions->activate();
+		nextHero->activate();
+		endTurn->activate();
 
 		minimap.activate();
 		heroList.activate();
@@ -642,16 +631,16 @@ void CAdvMapInt::deactivate()
 		scrollingDir = 0;
 
 		CCS->curh->changeGraphic(ECursor::ADVENTURE,0);
-		kingOverview.deactivate();
-		underground.deactivate();
-		questlog.deactivate();
-		sleepWake.deactivate();
-		moveHero.deactivate();
-		spellbook.deactivate();
-		advOptions.deactivate();
-		sysOptions.deactivate();
-		nextHero.deactivate();
-		endTurn.deactivate();
+		kingOverview->deactivate();
+		underground->deactivate();
+		questlog->deactivate();
+		sleepWake->deactivate();
+		moveHero->deactivate();
+		spellbook->deactivate();
+		advOptions->deactivate();
+		sysOptions->deactivate();
+		nextHero->deactivate();
+		endTurn->deactivate();
 		minimap.deactivate();
 		heroList.deactivate();
 		townList.deactivate();
@@ -668,16 +657,16 @@ void CAdvMapInt::showAll(SDL_Surface * to)
 	if(state != INGAME)
 		return;
 
-	kingOverview.showAll(to);
-	underground.showAll(to);
-	questlog.showAll(to);
-	sleepWake.showAll(to);
-	moveHero.showAll(to);
-	spellbook.showAll(to);
-	advOptions.showAll(to);
-	sysOptions.showAll(to);
-	nextHero.showAll(to);
-	endTurn.showAll(to);
+	kingOverview->showAll(to);
+	underground->showAll(to);
+	questlog->showAll(to);
+	sleepWake->showAll(to);
+	moveHero->showAll(to);
+	spellbook->showAll(to);
+	advOptions->showAll(to);
+	sysOptions->showAll(to);
+	nextHero->showAll(to);
+	endTurn->showAll(to);
 
 	minimap.showAll(to);
 	heroList.showAll(to);
@@ -788,8 +777,8 @@ void CAdvMapInt::centerOn(int3 on)
 
 	position = on;
 	updateScreen=true;
-	underground.setIndex(on.z,true); //change underground switch button image
-	underground.redraw();
+	underground->setIndex(on.z,true); //change underground switch button image
+	underground->redraw();
 	if (switchedLevels)
 		minimap.setLevel(position.z);
 }
@@ -1103,16 +1092,16 @@ void CAdvMapInt::setPlayer(PlayerColor Player)
 	player = Player;
 	graphics->blueToPlayersAdv(bg,player);
 
-	kingOverview.setPlayerColor(player);
-	underground.setPlayerColor(player);
-	questlog.setPlayerColor(player);
-	sleepWake.setPlayerColor(player);
-	moveHero.setPlayerColor(player);
-	spellbook.setPlayerColor(player);
-	sysOptions.setPlayerColor(player);
-	advOptions.setPlayerColor(player);
-	nextHero.setPlayerColor(player);
-	endTurn.setPlayerColor(player);
+	kingOverview->setPlayerColor(player);
+	underground->setPlayerColor(player);
+	questlog->setPlayerColor(player);
+	sleepWake->setPlayerColor(player);
+	moveHero->setPlayerColor(player);
+	spellbook->setPlayerColor(player);
+	sysOptions->setPlayerColor(player);
+	advOptions->setPlayerColor(player);
+	nextHero->setPlayerColor(player);
+	endTurn->setPlayerColor(player);
 	graphics->blueToPlayersAdv(resdatabar.bg,player);
 
 	//heroList.updateHList();
@@ -1536,19 +1525,20 @@ CAdventureOptions::CAdventureOptions():
 {
 	OBJ_CONSTRUCTION_CAPTURING_ALL;
 
-	exit = new CAdventureMapButton("","",boost::bind(&CAdventureOptions::close, this), 204, 313, "IOK6432.DEF",SDLK_RETURN);
+	exit = new CButton(Point(204, 313), "IOK6432.DEF", CButton::tooltip(), boost::bind(&CAdventureOptions::close, this), SDLK_RETURN);
 	exit->assignedKeys.insert(SDLK_ESCAPE);
 
-	scenInfo = new CAdventureMapButton("","", boost::bind(&CAdventureOptions::close, this), 24, 198, "ADVINFO.DEF",SDLK_i);
-	scenInfo->callback += CAdventureOptions::showScenarioInfo;
-	//viewWorld = new CAdventureMapButton("","",boost::bind(&CGuiHandler::popIntTotally, &GH, this), 204, 313, "IOK6432.DEF",SDLK_RETURN);
+	scenInfo = new CButton(Point(24, 198), "ADVINFO.DEF", CButton::tooltip(), [&]{ close(); }, SDLK_i);
+	scenInfo->addCallback(CAdventureOptions::showScenarioInfo);
+
+	//viewWorld = new CButton("","",boost::bind(&CGuiHandler::popIntTotally, &GH, this), 204, 313, "IOK6432.DEF",SDLK_RETURN);
 
-	puzzle = new CAdventureMapButton("","", boost::bind(&CAdventureOptions::close, this), 24, 81, "ADVPUZ.DEF");
-	puzzle->callback += boost::bind(&CPlayerInterface::showPuzzleMap, LOCPLINT);
+	puzzle = new CButton(Point(24, 81), "ADVPUZ.DEF", CButton::tooltip(), [&]{ close(); }, SDLK_p);
+	puzzle->addCallback(boost::bind(&CPlayerInterface::showPuzzleMap, LOCPLINT));
 
-	dig = new CAdventureMapButton("","", boost::bind(&CAdventureOptions::close, this), 24, 139, "ADVDIG.DEF");
+	dig = new CButton(Point(24, 139), "ADVDIG.DEF", CButton::tooltip(), [&]{ close(); }, SDLK_d);
 	if(const CGHeroInstance *h = adventureInt->curHero())
-		dig->callback += boost::bind(&CPlayerInterface::tryDiggging, LOCPLINT, h);
+		dig->addCallback(boost::bind(&CPlayerInterface::tryDiggging, LOCPLINT, h));
 	else
 		dig->block(true);
 }

+ 11 - 11
client/windows/CAdvmapInterface.h

@@ -32,7 +32,7 @@ class IShipyard;
 class CAdventureOptions : public CWindowObject
 {
 public:
-	CAdventureMapButton *exit, *viewWorld, *puzzle, *dig, *scenInfo, *replay;
+	CButton *exit, *viewWorld, *puzzle, *dig, *scenInfo, *replay;
 
 	CAdventureOptions();
 	static void showScenarioInfo();
@@ -110,16 +110,16 @@ public:
 	CMinimap minimap;
 	CGStatusBar statusbar;
 
-	CAdventureMapButton kingOverview,//- kingdom overview
-		underground,//- underground switch
-		questlog,//- questlog
-		sleepWake, //- sleep/wake hero
-		moveHero, //- move hero
-		spellbook,//- spellbook
-		advOptions, //- adventure options
-		sysOptions,//- system options
-		nextHero, //- next hero
-		endTurn;//- end turn
+	CButton * kingOverview;
+	CButton * underground;
+	CButton * questlog;
+	CButton * sleepWake;
+	CButton * moveHero;
+	CButton * spellbook;
+	CButton * advOptions;
+	CButton * sysOptions;
+	CButton * nextHero;
+	CButton * endTurn;
 
 	CTerrainRect terrain; //visible terrain
 	CResDataBar resdatabar;

+ 16 - 19
client/windows/CCastleInterface.cpp

@@ -843,7 +843,7 @@ void CCastleBuildings::enterTownHall()
 		else
 		{
 			LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[673]);
-			(dynamic_cast<CInfoWindow*>(GH.topInt()))->buttons[0]->callback += boost::bind(&CCastleBuildings::openTownHall, this);
+			dynamic_cast<CInfoWindow*>(GH.topInt())->buttons[0]->addCallback(boost::bind(&CCastleBuildings::openTownHall, this));
 		}
 	}
 	else
@@ -888,12 +888,12 @@ CCastleInterface::CCastleInterface(const CGTownInstance * Town, const CGTownInst
 	income = new CLabel(195, 443, FONT_SMALL, CENTER);
 	icon = new CAnimImage("ITPT", 0, 0, 15, 387);
 
-	exit = new CAdventureMapButton(CGI->generaltexth->tcommands[8], "", boost::bind(&CCastleInterface::close,this), 744, 544, "TSBTNS", SDLK_RETURN);
+	exit = new CButton(Point(744, 544), "TSBTNS", CButton::tooltip(CGI->generaltexth->tcommands[8]), [&]{close();}, SDLK_RETURN);
 	exit->assignedKeys.insert(SDLK_ESCAPE);
-	exit->setOffset(4);
+	exit->setImageOrder(4, 5, 6, 7);
 
-	split = new CAdventureMapButton(CGI->generaltexth->tcommands[3], "", boost::bind(&CGarrisonInt::splitClick,garr), 744, 382, "TSBTNS.DEF");
-	split->callback += boost::bind(&HeroSlots::splitClicked, heroes);
+	split = new CButton(Point(744, 382), "TSBTNS.DEF", CButton::tooltip(CGI->generaltexth->tcommands[3]), [&]{garr->splitClick();});
+	split->addCallback(boost::bind(&HeroSlots::splitClicked, heroes));
 	garr->addSplitBtn(split);
 
 	Rect barRect(9, 182, 732, 18);
@@ -1311,8 +1311,7 @@ CHallInterface::CHallInterface(const CGTownInstance *Town):
 	statusBar = new CGStatusBar(new CPicture(*background, barRect, 5, 556, false));
 
 	title = new CLabel(399, 12, FONT_MEDIUM, CENTER, Colors::WHITE, town->town->buildings.at(BuildingID(town->hallLevel()+BuildingID::VILLAGE_HALL))->Name());
-	exit = new CAdventureMapButton(CGI->generaltexth->hcommands[8], "",
-	           boost::bind(&CHallInterface::close,this), 748, 556, "TPMAGE1.DEF", SDLK_RETURN);
+	exit = new CButton(Point(748, 556), "TPMAGE1.DEF", CButton::tooltip(CGI->generaltexth->hcommands[8]), [&]{close();}, SDLK_RETURN);
 	exit->assignedKeys.insert(SDLK_ESCAPE);
 
 	auto & boxList = town->town->clientInfo.hallSlots;
@@ -1414,15 +1413,14 @@ CBuildWindow::CBuildWindow(const CGTownInstance *Town, const CBuilding * Buildin
 
 	if(!rightClick)
 	{	//normal window
-		buy = new CAdventureMapButton(boost::str(boost::format(CGI->generaltexth->allTexts[595]) % building->Name()),
-		          "", boost::bind(&CBuildWindow::buyFunc,this), 45, 446,"IBUY30", SDLK_RETURN);
+		std::string tooltipYes = boost::str(boost::format(CGI->generaltexth->allTexts[595]) % building->Name());
+		std::string tooltipNo  = boost::str(boost::format(CGI->generaltexth->allTexts[596]) % building->Name());
+
+		buy = new CButton(Point(45, 446), "IBUY30", CButton::tooltip(tooltipYes), [&]{ buyFunc(); }, SDLK_RETURN);
 		buy->borderColor = Colors::METALLIC_GOLD;
-		buy->borderEnabled = true;
 
-		cancel = new CAdventureMapButton(boost::str(boost::format(CGI->generaltexth->allTexts[596]) % building->Name()),
-		             "", boost::bind(&CBuildWindow::close,this), 290, 445, "ICANCEL", SDLK_ESCAPE);
+		cancel = new CButton(Point(290, 445), "ICANCEL", CButton::tooltip(tooltipNo), [&] { close();}, SDLK_ESCAPE);
 		cancel->borderColor = Colors::METALLIC_GOLD;
-		cancel->borderEnabled = true;
 		buy->block(state!=7 || LOCPLINT->playerID != town->tempOwner);
 	}
 }
@@ -1452,7 +1450,7 @@ CFortScreen::CFortScreen(const CGTownInstance * town):
 	title = new CLabel(400, 12, FONT_BIG, CENTER, Colors::WHITE, fortBuilding->Name());
 
 	std::string text = boost::str(boost::format(CGI->generaltexth->fcommands[6]) % fortBuilding->Name());
-	exit = new CAdventureMapButton(text, "", boost::bind(&CFortScreen::close,this) ,748, 556, "TPMAGE1", SDLK_RETURN);
+	exit = new CButton(Point(748, 556), "TPMAGE1", CButton::tooltip(text), [&]{ close(); }, SDLK_RETURN);
 	exit->assignedKeys.insert(SDLK_ESCAPE);
 
 	std::vector<Point> positions;
@@ -1651,7 +1649,6 @@ void CFortScreen::RecruitArea::clickRight(tribool down, bool previousState)
 
 CMageGuildScreen::CMageGuildScreen(CCastleInterface * owner,std::string imagem) :CWindowObject(BORDERED,imagem)
 {
-
 	OBJ_CONSTRUCTION_CAPTURING_ALL;
 
 	window = new CPicture(owner->town->town->clientInfo.guildWindow , 332, 76);
@@ -1662,7 +1659,7 @@ CMageGuildScreen::CMageGuildScreen(CCastleInterface * owner,std::string imagem)
 	Rect barRect(7, 556, 737, 18);
 	statusBar = new CGStatusBar(new CPicture(*background, barRect, 7, 556, false));
 
-	exit = new CAdventureMapButton(CGI->generaltexth->allTexts[593],"",boost::bind(&CMageGuildScreen::close,this), 748, 556,"TPMAGE1.DEF",SDLK_RETURN);
+	exit = new CButton(Point(748, 556), "TPMAGE1.DEF", CButton::tooltip(CGI->generaltexth->allTexts[593]), [&]{ close(); }, SDLK_RETURN);
 	exit->assignedKeys.insert(SDLK_ESCAPE);
 
 	std::vector<std::vector<Point> > positions;
@@ -1739,13 +1736,13 @@ CBlacksmithDialog::CBlacksmithDialog(bool possible, CreatureID creMachineID, Art
 	                boost::lexical_cast<std::string>(CGI->arth->artifacts[aid]->price));
 
 	std::string text = boost::str(boost::format(CGI->generaltexth->allTexts[595]) % creature->nameSing);
-	buy = new CAdventureMapButton(text,"",boost::bind(&CBlacksmithDialog::close, this), 42, 312,"IBUY30.DEF",SDLK_RETURN);
+	buy = new CButton(Point(42, 312), "IBUY30.DEF", CButton::tooltip(text), [&]{ close(); }, SDLK_RETURN);
 
 	text = boost::str(boost::format(CGI->generaltexth->allTexts[596]) % creature->nameSing);
-	cancel = new CAdventureMapButton(text,"",boost::bind(&CBlacksmithDialog::close, this), 224, 312,"ICANCEL.DEF",SDLK_ESCAPE);
+	cancel = new CButton(Point(224, 312), "ICANCEL.DEF", CButton::tooltip(text), [&]{ close(); }, SDLK_ESCAPE);
 
 	if(possible)
-		buy->callback += [=]{ LOCPLINT->cb->buyArtifact(LOCPLINT->cb->getHero(hid),aid); };
+		buy->addCallback([=]{ LOCPLINT->cb->buyArtifact(LOCPLINT->cb->getHero(hid),aid); });
 	else
 		buy->block(true);
 

+ 9 - 9
client/windows/CCastleInterface.h

@@ -3,7 +3,7 @@
 #include "../widgets/CGarrisonInt.h"
 #include "../widgets/Images.h"
 
-class CAdventureMapButton;
+class CButton;
 class CBuilding;
 class CCastleBuildings;
 class CCreaturePic;
@@ -203,8 +203,8 @@ class CCastleInterface : public CWindowObject, public CWindowWithGarrison
 
 	CTownInfo *hall, *fort;
 
-	CAdventureMapButton *exit;
-	CAdventureMapButton *split;
+	CButton *exit;
+	CButton *split;
 
 	std::vector<CCreaInfo*> creainfo;//small icons of creatures (bottom-left corner);
 
@@ -260,7 +260,7 @@ class CHallInterface : public CWindowObject
 	CLabel *title;
 	CGStatusBar *statusBar;
 	CMinorResDataBar * resdatabar;
-	CAdventureMapButton *exit;
+	CButton *exit;
 
 public:
 	CHallInterface(const CGTownInstance * Town); //c-tor
@@ -272,8 +272,8 @@ class CBuildWindow: public CWindowObject
 	const CGTownInstance *town;
 	const CBuilding *building;
 
-	CAdventureMapButton *buy;
-	CAdventureMapButton *cancel;
+	CButton *buy;
+	CButton *cancel;
 
 	std::string getTextForState(int state);
 	void buyFunc();
@@ -328,7 +328,7 @@ class CFortScreen : public CWindowObject
 	std::vector<RecruitArea*> recAreas;
 	CMinorResDataBar * resdatabar;
 	CGStatusBar *statusBar;
-	CAdventureMapButton *exit;
+	CButton *exit;
 
 	std::string getBgName(const CGTownInstance *town);
 
@@ -353,7 +353,7 @@ class CMageGuildScreen : public CWindowObject
 		void hover(bool on);
 	};
 	CPicture *window;
-	CAdventureMapButton *exit;
+	CButton *exit;
 	std::vector<Scroll *> spells;
 	CMinorResDataBar * resdatabar;
 	CGStatusBar *statusBar;
@@ -365,7 +365,7 @@ public:
 /// The blacksmith window where you can buy available in town war machine
 class CBlacksmithDialog : public CWindowObject
 {
-	CAdventureMapButton *buy, *cancel;
+	CButton *buy, *cancel;
 	CPicture *animBG;
 	CCreatureAnim * anim;
 	CLabel * title;

+ 9 - 9
client/windows/CCreatureWindow.cpp

@@ -196,9 +196,9 @@ void CStackWindow::setStackArtifact(const CArtifactInstance * art, Point artPos)
 
 		if (info->owner)
 		{
-			stackArtifactButton = new CAdventureMapButton(text["label"].String(), text["help"].String(),[=]{
-				removeStackArtifact(ArtifactPosition::CREATURE_SLOT);
-			}, artPos.x - 2 , artPos.y + 46, "stackWindow/cancelButton");
+			stackArtifactButton = new CButton(Point(artPos.x - 2 , artPos.y + 46), "stackWindow/cancelButton",
+			                                  CButton::tooltip(text),
+			                                  [=]{ removeStackArtifact(ArtifactPosition::CREATURE_SLOT); });
 		}
 	}
 
@@ -463,8 +463,8 @@ void CStackWindow::CWindowSection::createCommanderAbilities()
 	},
 	Point(38, 3+pos.h), Point(63, 0), 6, abilitiesCount);
 
-	auto leftBtn   = new CAdventureMapButton("", "", [=]{ list->moveToPrev(); }, 10,  pos.h + 6, "hsbtns3.def", SDLK_LEFT);
-	auto rightBtn = new CAdventureMapButton("", "", [=]{ list->moveToNext(); }, 411, pos.h + 6, "hsbtns5.def", SDLK_RIGHT);
+	auto leftBtn   = new CButton(Point(10,  pos.h + 6), "hsbtns3.def", CButton::tooltip(), [=]{ list->moveToPrev(); }, SDLK_LEFT);
+	auto rightBtn =  new CButton(Point(411, pos.h + 6), "hsbtns5.def", CButton::tooltip(), [=]{ list->moveToNext(); }, SDLK_RIGHT);
 
 	if (abilitiesCount <= 6)
 	{
@@ -531,7 +531,7 @@ void CStackWindow::CWindowSection::createButtonPanel()
 		{
 			LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[12], onDismiss, 0, false, std::vector<CComponent*>());
 		};
-		new CAdventureMapButton(CGI->generaltexth->zelp[445], onClick, 5, 5,"IVIEWCR2.DEF",SDLK_d);
+		new CButton(Point(5, 5),"IVIEWCR2.DEF", CGI->generaltexth->zelp[445], onClick, SDLK_d);
 	}
 	if (parent->info->upgradeInfo)
 	{
@@ -560,7 +560,7 @@ void CStackWindow::CWindowSection::createButtonPanel()
 				}
 				LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[207], onUpgrade, nullptr, true, resComps);
 			};
-			auto upgradeBtn = new CAdventureMapButton(CGI->generaltexth->zelp[446], onClick, 221 + i * 40, 5, "stackWindow/upgradeButton", SDLK_1);
+			auto upgradeBtn = new CButton(Point(221 + i * 40, 5), "stackWindow/upgradeButton", CGI->generaltexth->zelp[446], onClick, SDLK_1);
 
 			upgradeBtn->addOverlay(new CAnimImage("CPRSMALL", VLC->creh->creatures[upgradeInfo.info.newID[i]]->iconIndex));
 
@@ -583,13 +583,13 @@ void CStackWindow::CWindowSection::createButtonPanel()
 			};
 
 			const JsonNode & text = VLC->generaltexth->localizedTexts["creatureWindow"][btnIDs[i]];
-			parent->switchButtons[i] = new CAdventureMapButton(text["label"].String(), text["help"].String(), onSwitch, 302 + i*40, 5, "stackWindow/upgradeButton");
+			parent->switchButtons[i] = new CButton(Point(302 + i*40, 5), "stackWindow/upgradeButton", CButton::tooltip(text), onSwitch);
 			parent->switchButtons[i]->addOverlay(new CAnimImage("stackWindow/switchModeIcons", i));
 		}
 		parent->switchButtons[parent->activeTab]->disable();
 	}
 
-	auto exitBtn = new CAdventureMapButton(CGI->generaltexth->zelp[445], [=]{ parent->close(); }, 382, 5, "hsbtns.def", SDLK_RETURN);
+	auto exitBtn = new CButton(Point(382, 5), "hsbtns.def", CGI->generaltexth->zelp[445], [=]{ parent->close(); }, SDLK_RETURN);
 	exitBtn->assignedKeys.insert(SDLK_ESCAPE);
 }
 

+ 3 - 3
client/windows/CCreatureWindow.h

@@ -20,7 +20,7 @@ class CStackInstance;
 class CStack;
 struct UpgradeInfo;
 class CTabbedInt;
-class CAdventureMapButton;
+class CButton;
 
 class CClickableObject : public LRClickableAreaWText
 {
@@ -73,14 +73,14 @@ class CStackWindow : public CWindowObject
 
 	CAnimImage * stackArtifactIcon;
 	LRClickableAreaWTextComp * stackArtifactHelp;
-	CAdventureMapButton * stackArtifactButton;
+	CButton * stackArtifactButton;
 
 	std::unique_ptr<StackWindowInfo> info;
 	std::vector<BonusInfo> activeBonuses;
 	size_t activeTab;
 	CTabbedInt * commanderTab;
 
-	std::map<int, CAdventureMapButton *> switchButtons;
+	std::map<int, CButton *> switchButtons;
 
 	void setSelection(si32 newSkill, CClickableObject * newIcon);
 	CClickableObject * selectedIcon;

+ 27 - 23
client/windows/CHeroWindow.cpp

@@ -93,8 +93,11 @@ CHeroWindow::CHeroWindow(const CGHeroInstance *hero):
     CWindowObject(PLAYER_COLORED, "HeroScr4"),
 	heroWArt(this, hero)
 {
+	auto & heroscrn = CGI->generaltexth->heroscrn;
+
 	OBJ_CONSTRUCTION_CAPTURING_ALL;
 	garr = nullptr;
+	tacticsButton = nullptr;
 	curHero = hero;
 	listSelection = nullptr;
 
@@ -103,23 +106,21 @@ CHeroWindow::CHeroWindow(const CGHeroInstance *hero):
 	//artifs = new CArtifactsOfHero(pos.topLeft(), true);
 	ourBar = new CGStatusBar(7, 559, "ADROLLVR.bmp", 660); // new CStatusBar(pos.x+72, pos.y+567, "ADROLLVR.bmp", 660);
 
-	quitButton = new CAdventureMapButton(CGI->generaltexth->heroscrn[17], std::string(),boost::bind(&CHeroWindow::close,this), 609, 516, "hsbtns.def", SDLK_RETURN);
+	quitButton = new CButton(Point(609, 516), "hsbtns.def", CButton::tooltip(heroscrn[17]), [&]{ close(); }, SDLK_RETURN);
 	quitButton->assignedKeys.insert(SDLK_ESCAPE);
-	dismissButton = new CAdventureMapButton(std::string(), CGI->generaltexth->heroscrn[28], boost::bind(&CHeroWindow::dismissCurrent,this), 454, 429, "hsbtns2.def", SDLK_d);
-	questlogButton = new CAdventureMapButton(CGI->generaltexth->heroscrn[0], std::string(), boost::bind(&CHeroWindow::questlog,this), 314, 429, "hsbtns4.def", SDLK_q);
-
-	formations = new CHighlightableButtonsGroup(0);
-	formations->addButton(map_list_of(0,CGI->generaltexth->heroscrn[23]),CGI->generaltexth->heroscrn[29], "hsbtns6.def", 481, 483, 0, 0, SDLK_t);
-	formations->addButton(map_list_of(0,CGI->generaltexth->heroscrn[24]),CGI->generaltexth->heroscrn[30], "hsbtns7.def", 481, 519, 1, 0, SDLK_l);
+	dismissButton = new CButton(Point(454, 429), "hsbtns2.def", CButton::tooltip(heroscrn[28]), [&]{ dismissCurrent(); }, SDLK_d);
+	questlogButton = new CButton(Point(314, 429), "hsbtns4.def", CButton::tooltip(heroscrn[0]), [&]{ questlog(); }, SDLK_q);
 
-	tacticsButton = new CHighlightableButton(0, 0, map_list_of(0,CGI->generaltexth->heroscrn[26])(3,CGI->generaltexth->heroscrn[25]), CGI->generaltexth->heroscrn[31], false, "hsbtns8.def", nullptr, 539, 483, SDLK_b);
+	formations = new CToggleGroup(0);
+	formations->addToggle(0, new CToggleButton(Point(481, 483), "hsbtns6.def", std::make_pair(heroscrn[23], heroscrn[29]), 0, SDLK_t));
+	formations->addToggle(1, new CToggleButton(Point(481, 519), "hsbtns7.def", std::make_pair(heroscrn[24], heroscrn[30]), 0, SDLK_l));
 
 	if (hero->commander)
 	{
-		commanderButton = new CAdventureMapButton ("Commander", "Commander info", boost::bind(&CHeroWindow::commanderWindow, this), 317, 18, "chftke.def", SDLK_c, nullptr, false);
+		auto texts = CGI->generaltexth->localizedTexts["heroWindow"]["openCommander"];
+		commanderButton = new CButton (Point(317, 18), "chftke.def", CButton::tooltip(texts), [&]{ commanderWindow(); }, SDLK_c);
 	}
 
-
 	//right list of heroes
 	for(int i=0; i < std::min(LOCPLINT->cb->howManyHeroes(false), 8); i++)
 		heroList.push_back(new CHeroSwitcher(Point(612, 87 + i * 54), LOCPLINT->cb->getHeroBySerial(i, false)));
@@ -180,7 +181,9 @@ CHeroWindow::CHeroWindow(const CGHeroInstance *hero):
 }
 
 void CHeroWindow::update(const CGHeroInstance * hero, bool redrawNeeded /*= false*/)
-{	
+{
+	auto & heroscrn = CGI->generaltexth->heroscrn;
+
 	if(!hero) //something strange... no hero? it shouldn't happen
 	{
         logGlobal->errorStream() << "Set nullptr hero? no way...";
@@ -192,10 +195,11 @@ void CHeroWindow::update(const CGHeroInstance * hero, bool redrawNeeded /*= fals
 	specArea->text = curHero->type->specDescr;
 	specImage->setFrame(curHero->type->imageIndex);
 
-	tacticsButton->callback.clear();
-	tacticsButton->callback2.clear();
+	delete tacticsButton;
+	tacticsButton = new CToggleButton(Point(539, 483), "hsbtns8.def", std::make_pair(heroscrn[26], heroscrn[31]), 0, SDLK_b);
+	tacticsButton->addHoverText(CButton::HIGHLIGHTED, CGI->generaltexth->heroscrn[25]);
 
-	dismissButton->hoverTexts[0] = boost::str(boost::format(CGI->generaltexth->heroscrn[16]) % curHero->name % curHero->type->heroClass->name);
+	dismissButton->addHoverText(CButton::NORMAL, boost::str(boost::format(CGI->generaltexth->heroscrn[16]) % curHero->name % curHero->type->heroClass->name));
 	portraitArea->hoverText = boost::str(boost::format(CGI->generaltexth->allTexts[15]) % curHero->name % curHero->type->heroClass->name);
 	portraitArea->text = curHero->getBiography();
 	portraitImage->setFrame(curHero->portrait);
@@ -204,10 +208,11 @@ void CHeroWindow::update(const CGHeroInstance * hero, bool redrawNeeded /*= fals
 		OBJ_CONSTRUCTION_CAPTURING_ALL;
 		if(!garr)
 		{
+			std::string helpBox = heroscrn[32];
+			boost::algorithm::replace_first(helpBox, "%s", CGI->generaltexth->allTexts[43]);
+
 			garr = new CGarrisonInt(15, 485, 8, Point(), background->bg, Point(15,485), curHero);
-			auto split = new CAdventureMapButton(CGI->generaltexth->allTexts[256], CGI->generaltexth->heroscrn[32],
-					boost::bind(&CGarrisonInt::splitClick,garr), 539, 519, "hsbtns9.def", false, nullptr, false); //deleted by garrison destructor
-			boost::algorithm::replace_first(split->hoverTexts[0],"%s",CGI->generaltexth->allTexts[43]);
+			auto split = new CButton(Point(539, 519), "hsbtns9.def", CButton::tooltip(CGI->generaltexth->allTexts[256], helpBox), [&]{ garr->splitClick(); });
 
 			garr->addSplitBtn(split);
 		}
@@ -239,7 +244,7 @@ void CHeroWindow::update(const CGHeroInstance * hero, bool redrawNeeded /*= fals
 		secSkillAreas[g]->type = skill;
 		secSkillAreas[g]->bonusValue = level;
 		secSkillAreas[g]->text = CGI->generaltexth->skillInfoTexts[skill][level-1];
-		secSkillAreas[g]->hoverText = boost::str(boost::format(CGI->generaltexth->heroscrn[21]) % CGI->generaltexth->levels[level-1] % CGI->generaltexth->skillName[skill]);
+		secSkillAreas[g]->hoverText = boost::str(boost::format(heroscrn[21]) % CGI->generaltexth->levels[level-1] % CGI->generaltexth->skillName[skill]);
 		secSkillImages[g]->setFrame(skill*3 + level + 2);
 	}
 
@@ -274,14 +279,13 @@ void CHeroWindow::update(const CGHeroInstance * hero, bool redrawNeeded /*= fals
 	else
 	{
 		tacticsButton->block(false);
-		tacticsButton->callback = vstd::assigno(curHero->tacticFormationEnabled,true);
-		tacticsButton->callback2 = vstd::assigno(curHero->tacticFormationEnabled,false);
+		tacticsButton->addCallback( [&](bool on) {curHero->tacticFormationEnabled = on;});
+
 	}
 
 	//setting formations
-	formations->onChange = 0;
-	formations->select(curHero->formation,true);
-	formations->onChange = boost::bind(&CCallback::setFormation, LOCPLINT->cb.get(), curHero, _1);
+	formations->setSelected(curHero->formation);
+	formations->addCallback([&] (int value) { LOCPLINT->cb->setFormation(curHero, value); });
 
 	morale->set(&heroWArt);
 	luck->set(&heroWArt);

+ 7 - 7
client/windows/CHeroWindow.h

@@ -14,7 +14,7 @@
  *
  */
 
-class CAdventureMapButton;
+class CButton;
 struct SDL_Surface;
 class CGHeroInstance;
 class CDefHandler;
@@ -25,8 +25,8 @@ class LRClickableAreaWText;
 class LRClickableAreaWTextComp;
 class CArtifactsOfHero;
 class MoraleLuckBox;
-class CHighlightableButton;
-class CHighlightableButtonsGroup;
+class CToggleButton;
+class CToggleGroup;
 class CGStatusBar;
 
 /// Button which switches hero selection
@@ -56,7 +56,7 @@ class CHeroWindow: public CWindowObject, public CWindowWithGarrison, public CWin
 	CGStatusBar * ourBar; //heroWindow's statusBar
 
 	//buttons
-	//CAdventureMapButton * gar4button; //splitting
+	//CButton * gar4button; //splitting
 	std::vector<CHeroSwitcher *> heroList; //list of heroes
 	CPicture * listSelection; //selection border
 
@@ -74,10 +74,10 @@ class CHeroWindow: public CWindowObject, public CWindowWithGarrison, public CWin
 	std::vector<CAnimImage *> secSkillImages;
 	CHeroWithMaybePickedArtifact heroWArt;
 
-	CAdventureMapButton * quitButton, * dismissButton, * questlogButton, * commanderButton; //general
+	CButton * quitButton, * dismissButton, * questlogButton, * commanderButton; //general
 		
-	CHighlightableButton *tacticsButton; //garrison / formation handling;
-	CHighlightableButtonsGroup *formations;
+	CToggleButton *tacticsButton; //garrison / formation handling;
+	CToggleGroup *formations;
 
 public:
 	const CGHeroInstance * curHero;

+ 26 - 30
client/windows/CKingdomInterface.cpp

@@ -608,33 +608,29 @@ void CKingdomInterface::generateButtons()
 	ui32 footerPos = conf.go()->ac.overviewSize * 116;
 
 	//Main control buttons
-	btnHeroes = new CAdventureMapButton (CGI->generaltexth->overview[11], CGI->generaltexth->overview[6],
-	                                    boost::bind(&CKingdomInterface::activateTab, this, 0),748,28+footerPos,"OVBUTN1.DEF", SDLK_h);
+	btnHeroes = new CButton (Point(748, 28+footerPos), "OVBUTN1.DEF", CButton::tooltip(CGI->generaltexth->overview[11], CGI->generaltexth->overview[6]),
+	                         boost::bind(&CKingdomInterface::activateTab, this, 0), SDLK_h);
 	btnHeroes->block(true);
 
-	btnTowns = new CAdventureMapButton (CGI->generaltexth->overview[12], CGI->generaltexth->overview[7],
-	                                   boost::bind(&CKingdomInterface::activateTab, this, 1),748,64+footerPos,"OVBUTN6.DEF", SDLK_t);
+	btnTowns = new CButton (Point(748, 64+footerPos), "OVBUTN6.DEF", CButton::tooltip(CGI->generaltexth->overview[12], CGI->generaltexth->overview[7]),
+	                        boost::bind(&CKingdomInterface::activateTab, this, 1), SDLK_t);
 
-	btnExit = new CAdventureMapButton (CGI->generaltexth->allTexts[600],"",
-	                                  boost::bind(&CKingdomInterface::close, this),748,99+footerPos,"OVBUTN1.DEF", SDLK_RETURN);
+	btnExit = new CButton (Point(748,99+footerPos), "OVBUTN1.DEF", CButton::tooltip(CGI->generaltexth->allTexts[600]),
+	                       boost::bind(&CKingdomInterface::close, this), SDLK_RETURN);
 	btnExit->assignedKeys.insert(SDLK_ESCAPE);
-	btnExit->setOffset(3);
+	btnExit->setImageOrder(3, 4, 5, 6);
 
 	//Object list control buttons
-	dwellTop = new CAdventureMapButton ("", "", boost::bind(&CListBox::moveToPos, dwellingsList, 0),
-	                                   733, 4, "OVBUTN4.DEF");
+	dwellTop = new CButton (Point(733, 4), "OVBUTN4.DEF", CButton::tooltip(), [&]{ dwellingsList->moveToPos(0);});
 
-	dwellBottom = new CAdventureMapButton ("", "", boost::bind(&CListBox::moveToPos, dwellingsList, -1),
-	                                      733, footerPos+2, "OVBUTN4.DEF");
-	dwellBottom->setOffset(2);
+	dwellBottom = new CButton (Point(733, footerPos+2), "OVBUTN4.DEF", CButton::tooltip(), [&]{ dwellingsList->moveToPos(-1); });
+	dwellBottom->setImageOrder(2, 3, 4, 5);
 
-	dwellUp = new CAdventureMapButton ("", "", boost::bind(&CListBox::moveToPrev, dwellingsList),
-	                                  733, 24, "OVBUTN4.DEF");
-	dwellUp->setOffset(4);
+	dwellUp = new CButton (Point(733, 24), "OVBUTN4.DEF", CButton::tooltip(), [&]{ dwellingsList->moveToPrev(); });
+	dwellUp->setImageOrder(4, 5, 6, 7);
 
-	dwellDown = new CAdventureMapButton ("", "", boost::bind(&CListBox::moveToNext, dwellingsList),
-	                                    733, footerPos-18, "OVBUTN4.DEF");
-	dwellDown->setOffset(6);
+	dwellDown = new CButton (Point(733, footerPos-18), "OVBUTN4.DEF", CButton::tooltip(), [&]{ dwellingsList->moveToNext(); });
+	dwellDown->setImageOrder(6, 7, 8, 9);
 }
 
 void CKingdomInterface::activateTab(size_t which)
@@ -851,16 +847,16 @@ class BackpackTab : public CIntObject
 public:
 	CAnimImage * background;
 	std::vector<CArtPlace*> arts;
-	CAdventureMapButton *btnLeft;
-	CAdventureMapButton *btnRight;
+	CButton *btnLeft;
+	CButton *btnRight;
 
 	BackpackTab()
 	{
 		OBJ_CONSTRUCTION_CAPTURING_ALL;
 		background = new CAnimImage("OVSLOT", 5);
 		pos = background->pos;
-		btnLeft = new CAdventureMapButton(std::string(), std::string(), CFunctionList<void()>(), 269, 66, "HSBTNS3");
-		btnRight = new CAdventureMapButton(std::string(), std::string(), CFunctionList<void()>(), 675, 66, "HSBTNS5");
+		btnLeft = new CButton(Point(269, 66), "HSBTNS3", CButton::tooltip(), 0);
+		btnRight = new CButton(Point(675, 66), "HSBTNS5", CButton::tooltip(), 0);
 		for (size_t i=0; i<8; i++)
 			arts.push_back(new CArtPlace(Point(295+i*48, 65)));
 	}
@@ -894,21 +890,21 @@ CHeroItem::CHeroItem(const CGHeroInstance* Hero, CArtifactsOfHero::SCommonPart *
 
 	artsTabs = new CTabbedInt(boost::bind(&CHeroItem::onTabSelected, this, _1), boost::bind(&CHeroItem::onTabDeselected, this, _1));
 
-	artButtons = new CHighlightableButtonsGroup(0);
+	artButtons = new CToggleGroup(0);
 	for (size_t it = 0; it<3; it++)
 	{
 		int stringID[3] = {259, 261, 262};
 
-		std::map<int,std::string> tooltip;
-		tooltip[0] = CGI->generaltexth->overview[13+it];
+		std::string hover = CGI->generaltexth->overview[13+it];
 		std::string overlay = CGI->generaltexth->overview[8+it];
 
-		artButtons->addButton(tooltip, overlay, "OVBUTN3",364+it*112, 46, it);
-		artButtons->buttons[it]->addTextOverlay(CGI->generaltexth->allTexts[stringID[it]], FONT_SMALL, Colors::YELLOW);
+		auto button = new CToggleButton(Point(364+it*112, 46), "OVBUTN3", CButton::tooltip(hover, overlay), 0);
+		button->addTextOverlay(CGI->generaltexth->allTexts[stringID[it]], FONT_SMALL, Colors::YELLOW);
+		artButtons->addToggle(it, button);
 	}
-	artButtons->onChange += boost::bind(&CTabbedInt::setActive, artsTabs, _1);
-	artButtons->onChange += boost::bind(&CHeroItem::onArtChange, this, _1);
-	artButtons->select(0,0);
+	artButtons->addCallback(boost::bind(&CTabbedInt::setActive, artsTabs, _1));
+	artButtons->addCallback(boost::bind(&CHeroItem::onArtChange, this, _1));
+	artButtons->setSelected(0);
 
 	garr = new CGarrisonInt(6, 78, 4, Point(), nullptr, Point(), hero, nullptr, true, true);
 

+ 8 - 8
client/windows/CKingdomInterface.h

@@ -3,9 +3,9 @@
 #include "../widgets/CArtifactHolder.h"
 #include "../widgets/CGarrisonInt.h"
 
-class CAdventureMapButton;
+class CButton;
 class CAnimImage;
-class CHighlightableButtonsGroup;
+class CToggleGroup;
 class CResDataBar;
 class CSlider;
 class CTownInfo;
@@ -220,13 +220,13 @@ private:
 	CTabbedInt * tabArea;
 
 	//Main buttons
-	CAdventureMapButton *btnTowns;
-	CAdventureMapButton *btnHeroes;
-	CAdventureMapButton *btnExit;
+	CButton *btnTowns;
+	CButton *btnHeroes;
+	CButton *btnExit;
 
 	//Buttons for scrolling dwellings list
-	CAdventureMapButton *dwellUp, *dwellDown;
-	CAdventureMapButton *dwellTop, *dwellBottom;
+	CButton *dwellUp, *dwellDown;
+	CButton *dwellTop, *dwellBottom;
 
 	InfoBox * minesBox[7];
 
@@ -295,7 +295,7 @@ class CHeroItem : public CIntObject, public CWindowWithGarrison
 	CLabel *artsText;
 	CTabbedInt *artsTabs;
 
-	CHighlightableButtonsGroup *artButtons;
+	CToggleGroup *artButtons;
 	std::vector<InfoBox*> heroInfo;
 	MoraleLuckBox * morale, * luck;
 

+ 1 - 1
client/windows/CQuestLog.cpp

@@ -129,7 +129,7 @@ void CQuestLog::init()
 {
 	minimap = new CQuestMinimap (Rect (47, 33, 144, 144));
 	description = new CTextBox ("", Rect(245, 33, 350, 355), 1, FONT_MEDIUM, TOPLEFT, Colors::WHITE);
-	ok = new CAdventureMapButton("",CGI->generaltexth->zelp[445].second, boost::bind(&CQuestLog::close,this), 547, 401, "IOKAY.DEF", SDLK_RETURN);
+	ok = new CButton(Point(547, 401), "IOKAY.DEF", CGI->generaltexth->zelp[445], boost::bind(&CQuestLog::close,this), SDLK_RETURN);
 
 	if (quests.size() > QUEST_COUNT)
 		slider = new CSlider(203, 199, 230, boost::bind (&CQuestLog::sliderMoved, this, _1), QUEST_COUNT, quests.size(), false, 0);

+ 3 - 3
client/windows/CQuestLog.h

@@ -18,11 +18,11 @@
 
 class CCreature;
 class CStackInstance;
-class CAdventureMapButton;
+class CButton;
 class CGHeroInstance;
 class CComponent;
 class LRClickableAreaWText;
-class CAdventureMapButton;
+class CButton;
 class CPicture;
 class CCreaturePic;
 class LRClickableAreaWTextComp;
@@ -85,7 +85,7 @@ class CQuestLog : public CWindowObject
 	CTextBox * description;
 	CQuestMinimap * minimap;
 	CSlider * slider; //scrolls quests
-	CAdventureMapButton *ok;
+	CButton *ok;
 
 	void init ();
 public:

+ 16 - 16
client/windows/CTradeWindow.cpp

@@ -695,16 +695,16 @@ CMarketplaceWindow::CMarketplaceWindow(const IMarket *Market, const CGHeroInstan
 	initItems(false);
 	initItems(true);
 
-	ok = new CAdventureMapButton(CGI->generaltexth->zelp[600],boost::bind(&CGuiHandler::popIntTotally,&GH,this),516,520,"IOK6432.DEF",SDLK_RETURN);
+	ok = new CButton(Point(516, 520), "IOK6432.DEF", CGI->generaltexth->zelp[600], [&]{ close(); }, SDLK_RETURN);
 	ok->assignedKeys.insert(SDLK_ESCAPE);
-	deal = new CAdventureMapButton(CGI->generaltexth->zelp[595],boost::bind(&CMarketplaceWindow::makeDeal,this),307,520,"TPMRKB.DEF");
+	deal = new CButton(Point(307, 520), "TPMRKB.DEF", CGI->generaltexth->zelp[595], [&] { makeDeal(); } );
 	deal->block(true);
 
 	if(sliderNeeded)
 	{
 		slider = new CSlider(231,490,137,nullptr,0,0);
 		slider->moved = boost::bind(&CMarketplaceWindow::sliderMoved,this,_1);
-		max = new CAdventureMapButton(CGI->generaltexth->zelp[596],boost::bind(&CMarketplaceWindow::setMax,this),229,520,"IRCBTNS.DEF");
+		max = new CButton(Point(229, 520), "IRCBTNS.DEF", CGI->generaltexth->zelp[596], [&] { setMax(); });
 		max->block(true);
 	}
 	else
@@ -757,15 +757,15 @@ CMarketplaceWindow::CMarketplaceWindow(const IMarket *Market, const CGHeroInstan
 	int specialOffset = mode == EMarketMode::ARTIFACT_RESOURCE ? 35 : 0; //in selling artifacts mode we need to move res-res and art-res buttons down
 
 	if(printButtonFor(EMarketMode::RESOURCE_PLAYER))
-		new CAdventureMapButton(CGI->generaltexth->zelp[612],boost::bind(&CMarketplaceWindow::setMode,this, EMarketMode::RESOURCE_PLAYER), 18, 520,"TPMRKBU1.DEF");
+		new CButton(Point(18, 520),"TPMRKBU1.DEF", CGI->generaltexth->zelp[612], [&] { setMode(EMarketMode::RESOURCE_PLAYER);});
 	if(printButtonFor(EMarketMode::RESOURCE_RESOURCE))
-		new CAdventureMapButton(CGI->generaltexth->zelp[605],boost::bind(&CMarketplaceWindow::setMode,this, EMarketMode::RESOURCE_RESOURCE), 516, 450 + specialOffset,"TPMRKBU5.DEF");
+		new CButton(Point(516, 450 + specialOffset),"TPMRKBU5.DEF", CGI->generaltexth->zelp[605], [&] { setMode(EMarketMode::RESOURCE_RESOURCE);});
 	if(printButtonFor(EMarketMode::CREATURE_RESOURCE))
-		new CAdventureMapButton(CGI->generaltexth->zelp[599],boost::bind(&CMarketplaceWindow::setMode,this, EMarketMode::CREATURE_RESOURCE), 516, 485,"TPMRKBU4.DEF"); //was y=450, changed to not overlap res-res in some conditions
+		new CButton(Point(516, 485),"TPMRKBU4.DEF", CGI->generaltexth->zelp[599], [&] { setMode(EMarketMode::CREATURE_RESOURCE);});
 	if(printButtonFor(EMarketMode::RESOURCE_ARTIFACT))
-		new CAdventureMapButton(CGI->generaltexth->zelp[598],boost::bind(&CMarketplaceWindow::setMode,this, EMarketMode::RESOURCE_ARTIFACT), 18, 450 + specialOffset,"TPMRKBU2.DEF");
+		new CButton(Point(18, 450 + specialOffset),"TPMRKBU2.DEF", CGI->generaltexth->zelp[598], [&] { setMode(EMarketMode::RESOURCE_ARTIFACT);});
 	if(printButtonFor(EMarketMode::ARTIFACT_RESOURCE))
-		new CAdventureMapButton(CGI->generaltexth->zelp[613],boost::bind(&CMarketplaceWindow::setMode,this, EMarketMode::ARTIFACT_RESOURCE), 18, 485,"TPMRKBU3.DEF"); //was y=450, changed to not overlap res-art in some conditions
+		new CButton(Point(18, 485),"TPMRKBU3.DEF", CGI->generaltexth->zelp[613], [&] { setMode(EMarketMode::ARTIFACT_RESOURCE);});
 
 	updateTraderText();
 }
@@ -1111,10 +1111,10 @@ CAltarWindow::CAltarWindow(const IMarket *Market, const CGHeroInstance *Hero /*=
 
 		slider = new CSlider(231,481,137,nullptr,0,0);
 		slider->moved = boost::bind(&CAltarWindow::sliderMoved,this,_1);
-		max = new CAdventureMapButton(CGI->generaltexth->zelp[578],boost::bind(&CSlider::moveToMax, slider),147,520,"IRCBTNS.DEF");
+		max = new CButton(Point(147, 520), "IRCBTNS.DEF", CGI->generaltexth->zelp[578], boost::bind(&CSlider::moveToMax, slider));
 
 		sacrificedUnits.resize(GameConstants::ARMY_SIZE, 0);
-		sacrificeAll = new CAdventureMapButton(CGI->generaltexth->zelp[579],boost::bind(&CAltarWindow::SacrificeAll,this),393,520,"ALTARMY.DEF");
+		sacrificeAll = new CButton(Point(393, 520), "ALTARMY.DEF", CGI->generaltexth->zelp[579], boost::bind(&CAltarWindow::SacrificeAll,this));
 		sacrificeBackpack = nullptr;
 
 		initItems(true);
@@ -1128,9 +1128,9 @@ CAltarWindow::CAltarWindow(const IMarket *Market, const CGHeroInstance *Hero /*=
 		//%s's Creatures
 		new CLabel(302, 423, FONT_SMALL, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[478]);
 
-		sacrificeAll = new CAdventureMapButton(CGI->generaltexth->zelp[571], boost::bind(&CAltarWindow::SacrificeAll,this),393,520,"ALTFILL.DEF");
+		sacrificeAll = new CButton(Point(393, 520), "ALTFILL.DEF", CGI->generaltexth->zelp[571], boost::bind(&CAltarWindow::SacrificeAll,this));
 		sacrificeAll->block(hero->artifactsInBackpack.empty() && hero->artifactsWorn.empty());
-		sacrificeBackpack = new CAdventureMapButton(CGI->generaltexth->zelp[570],boost::bind(&CAltarWindow::SacrificeBackpack,this),147,520,"ALTEMBK.DEF");
+		sacrificeBackpack = new CButton(Point(147, 520), "ALTEMBK.DEF", CGI->generaltexth->zelp[570], boost::bind(&CAltarWindow::SacrificeBackpack,this));
 		sacrificeBackpack->block(hero->artifactsInBackpack.empty());
 
 		slider = nullptr;
@@ -1149,15 +1149,15 @@ CAltarWindow::CAltarWindow(const IMarket *Market, const CGHeroInstance *Hero /*=
 
 	new CGStatusBar(new CPicture(*background, Rect(8, pos.h - 26, pos.w - 16, 19), 8, pos.h - 26));
 
-	ok = new CAdventureMapButton(CGI->generaltexth->zelp[568],boost::bind(&CGuiHandler::popIntTotally,&GH,this),516,520,"IOK6432.DEF",SDLK_RETURN);
+	ok = new CButton(Point(516, 520), "IOK6432.DEF", CGI->generaltexth->zelp[568], [&]{ close();}, SDLK_RETURN);
 	ok->assignedKeys.insert(SDLK_ESCAPE);
 
-	deal = new CAdventureMapButton(CGI->generaltexth->zelp[585],boost::bind(&CAltarWindow::makeDeal,this),269,520,"ALTSACR.DEF");
+	deal = new CButton(Point(269, 520), "ALTSACR.DEF", CGI->generaltexth->zelp[585], boost::bind(&CAltarWindow::makeDeal,this));
 
 	if(Hero->getAlignment() != ::EAlignment::EVIL && Mode == EMarketMode::CREATURE_EXP)
-		new CAdventureMapButton(CGI->generaltexth->zelp[580], boost::bind(&CTradeWindow::setMode,this, EMarketMode::ARTIFACT_EXP), 516, 421, "ALTART.DEF");
+		new CButton(Point(516, 421), "ALTART.DEF", CGI->generaltexth->zelp[580], boost::bind(&CTradeWindow::setMode,this, EMarketMode::ARTIFACT_EXP));
 	if(Hero->getAlignment() != ::EAlignment::GOOD && Mode == EMarketMode::ARTIFACT_EXP)
-		new CAdventureMapButton(CGI->generaltexth->zelp[572], boost::bind(&CTradeWindow::setMode,this, EMarketMode::CREATURE_EXP), 516, 421, "ALTSACC.DEF");
+		new CButton(Point(516, 421), "ALTSACC.DEF", CGI->generaltexth->zelp[572], boost::bind(&CTradeWindow::setMode,this, EMarketMode::CREATURE_EXP));
 
 	expPerUnit.resize(GameConstants::ARMY_SIZE, 0);
 	getExpValues();

+ 2 - 2
client/windows/CTradeWindow.h

@@ -68,7 +68,7 @@ public:
 	EType itemsType[2];
 
 	EMarketMode::EMarketMode mode;//0 - res<->res; 1 - res<->plauer; 2 - buy artifact; 3 - sell artifact
-	CAdventureMapButton *ok, *max, *deal;
+	CButton *ok, *max, *deal;
 	CSlider *slider; //for choosing amount to be exchanged
 	bool readyToTrade;
 
@@ -137,7 +137,7 @@ public:
 	std::vector<int> sacrificedUnits, //[slot_nr] -> how many creatures from that slot will be sacrificed
 		expPerUnit;
 
-	CAdventureMapButton *sacrificeAll, *sacrificeBackpack;
+	CButton *sacrificeAll, *sacrificeBackpack;
 	CLabel *expToLevel, *expOnAltar;
 
 

+ 105 - 126
client/windows/GUIClasses.cpp

@@ -264,9 +264,9 @@ CRecruitmentWindow::CRecruitmentWindow(const CGDwelling *Dwelling, int Level, co
 	slider = new CSlider(176,279,135,nullptr,0,0,0,true);
 	slider->moved = boost::bind(&CRecruitmentWindow::sliderMoved,this, _1);
 
-	maxButton = new CAdventureMapButton(CGI->generaltexth->zelp[553],boost::bind(&CSlider::moveToMax,slider),134,313,"IRCBTNS.DEF",SDLK_m);
-	buyButton = new CAdventureMapButton(CGI->generaltexth->zelp[554],boost::bind(&CRecruitmentWindow::buy,this),212,313,"IBY6432.DEF",SDLK_RETURN);
-	cancelButton = new CAdventureMapButton(CGI->generaltexth->zelp[555],boost::bind(&CRecruitmentWindow::close,this),290,313,"ICN6432.DEF",SDLK_ESCAPE);
+	maxButton = new CButton(Point(134, 313), "IRCBTNS.DEF", CGI->generaltexth->zelp[553], boost::bind(&CSlider::moveToMax,slider), SDLK_m);
+	buyButton = new CButton(Point(212, 313), "IBY6432.DEF", CGI->generaltexth->zelp[554], boost::bind(&CRecruitmentWindow::buy,this), SDLK_RETURN);
+	cancelButton = new CButton(Point(290, 313), "ICN6432.DEF", CGI->generaltexth->zelp[555], boost::bind(&CRecruitmentWindow::close,this), SDLK_ESCAPE);
 
 	title = new CLabel(243, 32, FONT_BIG, CENTER, Colors::YELLOW);
 	availableValue = new CLabel(205, 253, FONT_SMALL, CENTER, Colors::WHITE);
@@ -375,8 +375,8 @@ CSplitWindow::CSplitWindow(const CCreature * creature, std::function<void(int, i
 	int leftMax = total - rightMin;
 	int rightMax = total - leftMin;
 
-	ok = new CAdventureMapButton("", "", boost::bind(&CSplitWindow::apply, this), 20, 263, "IOK6432", SDLK_RETURN);
-	cancel = new CAdventureMapButton("", "", boost::bind(&CSplitWindow::close, this), 214, 263, "ICN6432", SDLK_ESCAPE);
+	ok = new CButton(Point(20, 263), "IOK6432", CButton::tooltip(), boost::bind(&CSplitWindow::apply, this), SDLK_RETURN);
+	cancel = new CButton(Point(214, 263), "ICN6432", CButton::tooltip(), boost::bind(&CSplitWindow::close, this), SDLK_ESCAPE);
 
 	int sliderPositions = total - leftMin - rightMin;
 
@@ -442,7 +442,7 @@ CLevelWindow::CLevelWindow(const CGHeroInstance *hero, PrimarySkill::PrimarySkil
 	LOCPLINT->showingDialog->setn(true);
 
 	new CAnimImage("PortraitsLarge", hero->portrait, 0, 170, 66);
-	new CAdventureMapButton("", "", boost::bind(&CLevelWindow::close, this), 297, 413, "IOKAY", SDLK_RETURN);
+	new CButton(Point(297, 413), "IOKAY",  CButton::tooltip(), boost::bind(&CLevelWindow::close, this), SDLK_RETURN);
 
 	//%s has gained a level.
 	new CLabel(192, 33, FONT_MEDIUM, CENTER, Colors::WHITE,
@@ -536,96 +536,80 @@ CSystemOptionsWindow::CSystemOptionsWindow():
 	rightGroup->add(282, 217, texts["fullscreenButton"]["label"].String());
 
 	//setting up buttons
-	load = new CAdventureMapButton (CGI->generaltexth->zelp[321].first, CGI->generaltexth->zelp[321].second,
-									boost::bind(&CSystemOptionsWindow::bloadf, this), 246,  298, "SOLOAD.DEF", SDLK_l);
-	load->swappedImages = true;
-	load->update();
-
-	save = new CAdventureMapButton (CGI->generaltexth->zelp[322].first, CGI->generaltexth->zelp[322].second,
-	                                boost::bind(&CSystemOptionsWindow::bsavef, this), 357, 298, "SOSAVE.DEF", SDLK_s);
-	save->swappedImages = true;
-	save->update();
-
-	restart = new CAdventureMapButton (CGI->generaltexth->zelp[323].first, CGI->generaltexth->zelp[323].second,
-									   boost::bind(&CSystemOptionsWindow::brestartf, this), 246, 357, "SORSTRT", SDLK_r);
-	restart->swappedImages = true;
-	restart->update();
-
-	mainMenu = new CAdventureMapButton (CGI->generaltexth->zelp[320].first, CGI->generaltexth->zelp[320].second,
-	                                    boost::bind(&CSystemOptionsWindow::bmainmenuf, this), 357, 357, "SOMAIN.DEF", SDLK_m);
-	mainMenu->swappedImages = true;
-	mainMenu->update();
-
-	quitGame = new CAdventureMapButton (CGI->generaltexth->zelp[324].first, CGI->generaltexth->zelp[324].second,
-	                                    boost::bind(&CSystemOptionsWindow::bquitf, this), 246, 415, "soquit.def", SDLK_q);
-	quitGame->swappedImages = true;
-	quitGame->update();
-	backToMap = new CAdventureMapButton (CGI->generaltexth->zelp[325].first, CGI->generaltexth->zelp[325].second,
-	                                     boost::bind(&CSystemOptionsWindow::breturnf, this), 357, 415, "soretrn.def", SDLK_RETURN);
-	backToMap->swappedImages = true;
-	backToMap->update();
+	load = new CButton (Point(246,  298), "SOLOAD.DEF", CGI->generaltexth->zelp[321], [&] { bloadf(); }, SDLK_l);
+	load->setImageOrder(1, 0, 2, 3);
+
+	save = new CButton (Point(357, 298), "SOSAVE.DEF", CGI->generaltexth->zelp[322], [&] { bsavef(); }, SDLK_s);
+	save->setImageOrder(1, 0, 2, 3);
+
+	restart = new CButton (Point(246, 357), "SORSTRT", CGI->generaltexth->zelp[323], [&] { brestartf(); }, SDLK_r);
+	restart->setImageOrder(1, 0, 2, 3);
+
+	mainMenu = new CButton (Point(357, 357), "SOMAIN.DEF", CGI->generaltexth->zelp[320], [&] { bmainmenuf(); }, SDLK_m);
+	mainMenu->setImageOrder(1, 0, 2, 3);
+
+	quitGame = new CButton (Point(246, 415), "soquit.def", CGI->generaltexth->zelp[324], [&] { bquitf(); }, SDLK_q);
+	quitGame->setImageOrder(1, 0, 2, 3);
+
+	backToMap = new CButton ( Point(357, 415), "soretrn.def", CGI->generaltexth->zelp[325], [&] { breturnf(); }, SDLK_RETURN);
+	backToMap->setImageOrder(1, 0, 2, 3);
 	backToMap->assignedKeys.insert(SDLK_ESCAPE);
 
-	heroMoveSpeed = new CHighlightableButtonsGroup(0);
-	heroMoveSpeed->addButton(boost::assign::map_list_of(0,CGI->generaltexth->zelp[349].second),CGI->generaltexth->zelp[349].second, "sysopb1.def", 28, 77, 1);
-	heroMoveSpeed->addButton(boost::assign::map_list_of(0,CGI->generaltexth->zelp[350].second),CGI->generaltexth->zelp[350].second, "sysopb2.def", 76, 77, 2);
-	heroMoveSpeed->addButton(boost::assign::map_list_of(0,CGI->generaltexth->zelp[351].second),CGI->generaltexth->zelp[351].second, "sysopb3.def", 124, 77, 4);
-	heroMoveSpeed->addButton(boost::assign::map_list_of(0,CGI->generaltexth->zelp[352].second),CGI->generaltexth->zelp[352].second, "sysopb4.def", 172, 77, 8);
-	heroMoveSpeed->select(settings["adventure"]["heroSpeed"].Float(), 1);
-	heroMoveSpeed->onChange = boost::bind(&CSystemOptionsWindow::setHeroMoveSpeed, this, _1);
-
-	mapScrollSpeed = new CHighlightableButtonsGroup(0);
-	mapScrollSpeed->addButton(boost::assign::map_list_of(0,CGI->generaltexth->zelp[357].second),CGI->generaltexth->zelp[357].second, "sysopb9.def", 28, 210, 1);
-	mapScrollSpeed->addButton(boost::assign::map_list_of(0,CGI->generaltexth->zelp[358].second),CGI->generaltexth->zelp[358].second, "sysob10.def", 92, 210, 2);
-	mapScrollSpeed->addButton(boost::assign::map_list_of(0,CGI->generaltexth->zelp[359].second),CGI->generaltexth->zelp[359].second, "sysob11.def", 156, 210, 4);
-	mapScrollSpeed->select(settings["adventure"]["scrollSpeed"].Float(), 1);
-	mapScrollSpeed->onChange = boost::bind(&CSystemOptionsWindow::setMapScrollingSpeed, this, _1);
-
-	musicVolume = new CHighlightableButtonsGroup(0, true);
+	heroMoveSpeed = new CToggleGroup(0);
+	heroMoveSpeed->addToggle(1, new CToggleButton(Point( 28, 77), "sysopb1.def", CGI->generaltexth->zelp[349]));
+	heroMoveSpeed->addToggle(2, new CToggleButton(Point( 76, 77), "sysopb2.def", CGI->generaltexth->zelp[350]));
+	heroMoveSpeed->addToggle(4, new CToggleButton(Point(124, 77), "sysopb3.def", CGI->generaltexth->zelp[351]));
+	heroMoveSpeed->addToggle(8, new CToggleButton(Point(172, 77), "sysopb4.def", CGI->generaltexth->zelp[352]));
+	heroMoveSpeed->setSelected(settings["adventure"]["heroSpeed"].Float());
+	heroMoveSpeed->addCallback(boost::bind(&CSystemOptionsWindow::setHeroMoveSpeed, this, _1));
+
+	mapScrollSpeed = new CToggleGroup(0);
+	mapScrollSpeed->addToggle(1, new CToggleButton(Point( 28, 210), "sysopb9.def", CGI->generaltexth->zelp[357]));
+	mapScrollSpeed->addToggle(2, new CToggleButton(Point( 92, 210), "sysob10.def", CGI->generaltexth->zelp[358]));
+	mapScrollSpeed->addToggle(4, new CToggleButton(Point(156, 210), "sysob11.def", CGI->generaltexth->zelp[359]));
+	mapScrollSpeed->setSelected(settings["adventure"]["scrollSpeed"].Float());
+	mapScrollSpeed->addCallback(boost::bind(&CSystemOptionsWindow::setMapScrollingSpeed, this, _1));
+
+	musicVolume = new CToggleGroup(0, true);
 	for(int i=0; i<10; ++i)
-		musicVolume->addButton(boost::assign::map_list_of(0,CGI->generaltexth->zelp[326+i].second),CGI->generaltexth->zelp[326+i].second, "syslb.def", 29 + 19*i, 359, i*11);
+		musicVolume->addToggle(i*11, new CToggleButton(Point(29 + 19*i, 359), "syslb.def", CGI->generaltexth->zelp[326+i]));
 
-	musicVolume->select(CCS->musich->getVolume(), 1);
-	musicVolume->onChange = boost::bind(&CSystemOptionsWindow::setMusicVolume, this, _1);
+	musicVolume->setSelected(CCS->musich->getVolume());
+	musicVolume->addCallback(boost::bind(&CSystemOptionsWindow::setMusicVolume, this, _1));
 
-	effectsVolume = new CHighlightableButtonsGroup(0, true);
+	effectsVolume = new CToggleGroup(0, true);
 	for(int i=0; i<10; ++i)
-		effectsVolume->addButton(boost::assign::map_list_of(0,CGI->generaltexth->zelp[336+i].second),CGI->generaltexth->zelp[336+i].second, "syslb.def", 29 + 19*i, 425, i*11);
+		effectsVolume->addToggle(i*11, new CToggleButton(Point(29 + 19*i, 425), "syslb.def", CGI->generaltexth->zelp[336+i]));
 
-	effectsVolume->select(CCS->soundh->getVolume(), 1);
-	effectsVolume->onChange = boost::bind(&CSystemOptionsWindow::setSoundVolume, this, _1);
+	effectsVolume->setSelected(CCS->soundh->getVolume());
+	effectsVolume->addCallback(boost::bind(&CSystemOptionsWindow::setSoundVolume, this, _1));
 
-	showReminder = new CHighlightableButton(
-		boost::bind(&CSystemOptionsWindow::toggleReminder, this, true), boost::bind(&CSystemOptionsWindow::toggleReminder, this, false),
-		std::map<int,std::string>(), CGI->generaltexth->zelp[361].second, false, "sysopchk.def", nullptr, 246, 87, false);
+	showReminder = new CToggleButton(Point(246, 87), "sysopchk.def", CGI->generaltexth->zelp[361],
+	        [&] (bool value) { toggleReminder(value);});
 
-	quickCombat = new CHighlightableButton(
-		boost::bind(&CSystemOptionsWindow::toggleQuickCombat, this, true), boost::bind(&CSystemOptionsWindow::toggleQuickCombat, this, false),
-		std::map<int,std::string>(), CGI->generaltexth->zelp[362].second, false, "sysopchk.def", nullptr, 246, 87+32, false);
+	quickCombat = new CToggleButton(Point(246, 87+32), "sysopchk.def", CGI->generaltexth->zelp[362],
+	        [&] (bool value) { toggleQuickCombat(value);});
 
-	spellbookAnim = new CHighlightableButton(
-		boost::bind(&CSystemOptionsWindow::toggleSpellbookAnim, this, true), boost::bind(&CSystemOptionsWindow::toggleSpellbookAnim, this, false),
-		std::map<int,std::string>(), CGI->generaltexth->zelp[364].second, false, "sysopchk.def", nullptr, 246, 87+64, false);
+	spellbookAnim = new CToggleButton(Point(246, 87+64), "sysopchk.def", CGI->generaltexth->zelp[364],
+	        [&] (bool value) { toggleSpellbookAnim(value);});
 
-	newCreatureWin = new CHighlightableButton(
-		boost::bind(&CSystemOptionsWindow::toggleCreatureWin, this, true), boost::bind(&CSystemOptionsWindow::toggleCreatureWin, this, false),
-		std::map<int,std::string>(), texts["creatureWindowButton"]["help"].String(), false, "sysopchk.def", nullptr, 246, 183, false);
+	newCreatureWin = new CToggleButton(Point(246, 183), "sysopchk.def", CButton::tooltip(texts["creatureWindowButton"]),
+	        [&] (bool value) { toggleCreatureWin(value);});
 
-	fullscreen = new CHighlightableButton(
-		boost::bind(&CSystemOptionsWindow::toggleFullscreen, this, true), boost::bind(&CSystemOptionsWindow::toggleFullscreen, this, false),
-		std::map<int,std::string>(), texts["fullscreenButton"]["help"].String(), false, "sysopchk.def", nullptr, 246, 215, false);
+	fullscreen = new CToggleButton(Point(246, 215), "sysopchk.def", CButton::tooltip(texts["fullscreenButton"]),
+	        [&] (bool value) { toggleFullscreen(value);});
 
-	showReminder->select(settings["adventure"]["heroReminder"].Bool());
-	quickCombat->select(settings["adventure"]["quickCombat"].Bool());
-	spellbookAnim->select(settings["video"]["spellbookAnimation"].Bool());
-	newCreatureWin->select(settings["general"]["classicCreatureWindow"].Bool());
-	fullscreen->select(settings["video"]["fullscreen"].Bool());
+	showReminder->setSelected(settings["adventure"]["heroReminder"].Bool());
+	quickCombat->setSelected(settings["adventure"]["quickCombat"].Bool());
+	spellbookAnim->setSelected(settings["video"]["spellbookAnimation"].Bool());
+	newCreatureWin->setSelected(settings["general"]["classicCreatureWindow"].Bool());
+	fullscreen->setSelected(settings["video"]["fullscreen"].Bool());
 
-	onFullscreenChanged([&](const JsonNode &newState){ fullscreen->select(newState.Bool());});
+	onFullscreenChanged([&](const JsonNode &newState){ fullscreen->setSelected(newState.Bool());});
 
-	gameResButton = new CAdventureMapButton("", texts["resolutionButton"]["help"].String(),
+	gameResButton = new CButton(Point(28, 275),"SYSOB12", CButton::tooltip(texts["resolutionButton"]),
 	                                        boost::bind(&CSystemOptionsWindow::selectGameRes, this),
-	                                        28, 275,"SYSOB12", SDLK_g);
+	                                        SDLK_g);
 
 	std::string resText;
 	resText += boost::lexical_cast<std::string>(settings["video"]["screenRes"]["width"].Float());
@@ -762,30 +746,30 @@ CTavernWindow::CTavernWindow(const CGObjectInstance *TavernObj):
 	new CTextBox(LOCPLINT->cb->getTavernGossip(tavernObj), Rect(32, 190, 330, 68), 0, FONT_SMALL, CENTER, Colors::WHITE);
 
 	new CGStatusBar(new CPicture(*background, Rect(8, pos.h - 26, pos.w - 16, 19), 8, pos.h - 26));
-	cancel = new CAdventureMapButton(CGI->generaltexth->tavernInfo[7],"", boost::bind(&CTavernWindow::close, this), 310, 428, "ICANCEL.DEF", SDLK_ESCAPE);
-	recruit = new CAdventureMapButton("", "", boost::bind(&CTavernWindow::recruitb, this), 272, 355, "TPTAV01.DEF", SDLK_RETURN);
-	thiefGuild = new CAdventureMapButton(CGI->generaltexth->tavernInfo[5],"", boost::bind(&CTavernWindow::thievesguildb, this), 22, 428, "TPTAV02.DEF", SDLK_t);
+	cancel = new CButton(Point(310, 428), "ICANCEL.DEF", CButton::tooltip(CGI->generaltexth->tavernInfo[7]), boost::bind(&CTavernWindow::close, this), SDLK_ESCAPE);
+	recruit = new CButton(Point(272, 355), "TPTAV01.DEF", CButton::tooltip(), boost::bind(&CTavernWindow::recruitb, this), SDLK_RETURN);
+	thiefGuild = new CButton(Point(22, 428), "TPTAV02.DEF", CButton::tooltip(CGI->generaltexth->tavernInfo[5]), boost::bind(&CTavernWindow::thievesguildb, this), SDLK_t);
 
 	if(LOCPLINT->cb->getResourceAmount(Res::GOLD) < 2500) //not enough gold
 	{
-		recruit->hoverTexts[0] = CGI->generaltexth->tavernInfo[0]; //Cannot afford a Hero
+		recruit->addHoverText(CButton::NORMAL, CGI->generaltexth->tavernInfo[0]); //Cannot afford a Hero
 		recruit->block(true);
 	}
 	else if(LOCPLINT->castleInt && LOCPLINT->cb->howManyHeroes(true) >= VLC->modh->settings.MAX_HEROES_AVAILABLE_PER_PLAYER)
 	{
-		recruit->hoverTexts[0] = CGI->generaltexth->tavernInfo[1]; //Cannot recruit. You already have %d Heroes.
-		boost::algorithm::replace_first(recruit->hoverTexts[0],"%d",boost::lexical_cast<std::string>(LOCPLINT->cb->howManyHeroes(true)));
+		//Cannot recruit. You already have %d Heroes.
+		recruit->addHoverText(CButton::NORMAL, boost::str(boost::format(CGI->generaltexth->tavernInfo[1]) % LOCPLINT->cb->howManyHeroes(true)));
 		recruit->block(true);
 	}
 	else if((!LOCPLINT->castleInt) && LOCPLINT->cb->howManyHeroes(false) >= VLC->modh->settings.MAX_HEROES_ON_MAP_PER_PLAYER)
 	{
-		recruit->hoverTexts[0] = CGI->generaltexth->tavernInfo[1]; //Cannot recruit. You already have %d Heroes.
-		boost::algorithm::replace_first(recruit->hoverTexts[0], "%d", boost::lexical_cast<std::string>(LOCPLINT->cb->howManyHeroes(false)));
+		//Cannot recruit. You already have %d Heroes.
+		recruit->addHoverText(CButton::NORMAL, boost::str(boost::format(CGI->generaltexth->tavernInfo[1]) % LOCPLINT->cb->howManyHeroes(false)));
 		recruit->block(true);
 	}
 	else if(LOCPLINT->castleInt && LOCPLINT->castleInt->town->visitingHero)
 	{
-		recruit->hoverTexts[0] = CGI->generaltexth->tavernInfo[2]; //Cannot recruit. You already have a Hero in this town.
+		recruit->addHoverText(CButton::NORMAL, CGI->generaltexth->tavernInfo[2]); //Cannot recruit. You already have a Hero in this town.
 		recruit->block(true);
 	}
 	else
@@ -831,9 +815,8 @@ void CTavernWindow::show(SDL_Surface * to)
 			// Selected hero just changed. Update RECRUIT button hover text if recruitment is allowed.
 			oldSelected = selected;
 
-			recruit->hoverTexts[0] = CGI->generaltexth->tavernInfo[3]; //Recruit %s the %s
-			boost::algorithm::replace_first(recruit->hoverTexts[0],"%s",sel->h->name);
-			boost::algorithm::replace_first(recruit->hoverTexts[0],"%s",sel->h->type->heroClass->name);
+			//Recruit %s the %s
+			recruit->addHoverText(CButton::NORMAL, boost::str(boost::format(CGI->generaltexth->tavernInfo[3]) % sel->h->name % sel->h->type->heroClass->name));
 		}
 
 		printAtMiddleWBLoc(sel->description, 146, 395, FONT_SMALL, 200, Colors::WHITE, to);
@@ -1038,20 +1021,20 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2,
 	}
 
 	//buttons
-	quit = new CAdventureMapButton(CGI->generaltexth->zelp[600], boost::bind(&CExchangeWindow::close, this), 732, 567, "IOKAY.DEF", SDLK_RETURN);
+	quit = new CButton(Point(732, 567), "IOKAY.DEF", CGI->generaltexth->zelp[600], boost::bind(&CExchangeWindow::close, this), SDLK_RETURN);
 	if(queryID.getNum() > 0)
-		quit->callback += [=]{ LOCPLINT->cb->selectionMade(0, queryID); };
+		quit->addCallback([=]{ LOCPLINT->cb->selectionMade(0, queryID); });
 
-	questlogButton[0] = new CAdventureMapButton(CGI->generaltexth->heroscrn[0], "", boost::bind(&CExchangeWindow::questlog,this, 0), 10,  44, "hsbtns4.def");
-	questlogButton[1] = new CAdventureMapButton(CGI->generaltexth->heroscrn[0], "", boost::bind(&CExchangeWindow::questlog,this, 1), 740, 44, "hsbtns4.def");
+	questlogButton[0] = new CButton(Point( 10, 44), "hsbtns4.def", CButton::tooltip(CGI->generaltexth->heroscrn[0]), boost::bind(&CExchangeWindow::questlog,this, 0));
+	questlogButton[1] = new CButton(Point(740, 44), "hsbtns4.def", CButton::tooltip(CGI->generaltexth->heroscrn[0]), boost::bind(&CExchangeWindow::questlog,this, 1));
 
 	Rect barRect(5, 578, 725, 18);
 	ourBar = new CGStatusBar(new CPicture(*background, barRect, 5, 578, false));
 
 	//garrison interface
 	garr = new CGarrisonInt(69, 131, 4, Point(418,0), *background, Point(69,131), heroInst[0],heroInst[1], true, true);
-	garr->addSplitBtn(new CAdventureMapButton(CGI->generaltexth->tcommands[3], "", boost::bind(&CGarrisonInt::splitClick, garr),  10, 132, "TSBTNS.DEF"));
-	garr->addSplitBtn(new CAdventureMapButton(CGI->generaltexth->tcommands[3], "", boost::bind(&CGarrisonInt::splitClick, garr), 740, 132, "TSBTNS.DEF"));
+	garr->addSplitBtn(new CButton( Point( 10, 132), "TSBTNS.DEF", CButton::tooltip(CGI->generaltexth->tcommands[3]), boost::bind(&CGarrisonInt::splitClick, garr)));
+	garr->addSplitBtn(new CButton( Point(740, 132), "TSBTNS.DEF", CButton::tooltip(CGI->generaltexth->tcommands[3]), boost::bind(&CGarrisonInt::splitClick, garr)));
 }
 
 CExchangeWindow::~CExchangeWindow() //d-tor
@@ -1084,9 +1067,9 @@ CShipyardWindow::CShipyardWindow(const std::vector<si32> &cost, int state, int b
 	goldPic = new CAnimImage("RESOURCE", Res::GOLD, 0, 100, 244);
 	woodPic = new CAnimImage("RESOURCE", Res::WOOD, 0, 196, 244);
 
-	quit = new CAdventureMapButton(CGI->generaltexth->allTexts[599], "", boost::bind(&CShipyardWindow::close, this), 224, 312, "ICANCEL", SDLK_RETURN);
-	build = new CAdventureMapButton(CGI->generaltexth->allTexts[598], "", boost::bind(&CShipyardWindow::close, this), 42, 312, "IBUY30", SDLK_RETURN);
-	build->callback += onBuy;
+	quit  = new CButton( Point(224, 312), "ICANCEL", CButton::tooltip(CGI->generaltexth->allTexts[599]), boost::bind(&CShipyardWindow::close, this), SDLK_RETURN);
+	build = new CButton( Point( 42, 312), "IBUY30",  CButton::tooltip(CGI->generaltexth->allTexts[598]), boost::bind(&CShipyardWindow::close, this),SDLK_RETURN);
+	build->addCallback(onBuy);
 
 	for(Res::ERes i = Res::WOOD; i <= Res::GOLD; vstd::advance(i, 1))
 	{
@@ -1111,10 +1094,9 @@ CPuzzleWindow::CPuzzleWindow(const int3 &GrailPos, double discoveredRatio):
 	OBJ_CONSTRUCTION_CAPTURING_ALL;
 	CCS->soundh->playSound(soundBase::OBELISK);
 
-	quitb = new CAdventureMapButton(CGI->generaltexth->allTexts[599], "", boost::bind(&CPuzzleWindow::close, this), 670, 538, "IOK6432.DEF", SDLK_RETURN);
+	quitb = new CButton(Point(670, 538), "IOK6432.DEF", CButton::tooltip(CGI->generaltexth->allTexts[599]), boost::bind(&CPuzzleWindow::close, this), SDLK_RETURN);
 	quitb->assignedKeys.insert(SDLK_ESCAPE);
 	quitb->borderColor = Colors::METALLIC_GOLD;
-	quitb->borderEnabled = true;
 
 	new CPicture("PUZZLOGO", 607, 3);
 	new CLabel(700, 95, FONT_BIG, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[463]);
@@ -1253,10 +1235,10 @@ CTransformerWindow::CTransformerWindow(const CGHeroInstance * _hero, const CGTow
 		if ( army->getCreature(SlotID(i)) )
 			items.push_back(new CItem(this, army->getStackCount(SlotID(i)), i));
 
-	all    = new CAdventureMapButton(CGI->generaltexth->zelp[590],boost::bind(&CTransformerWindow::addAll,this),     146,416,"ALTARMY.DEF",SDLK_a);
-	convert= new CAdventureMapButton(CGI->generaltexth->zelp[591],boost::bind(&CTransformerWindow::makeDeal,this),   269,416,"ALTSACR.DEF",SDLK_RETURN);
-	cancel = new CAdventureMapButton(CGI->generaltexth->zelp[592],boost::bind(&CTransformerWindow::close, this),392,416,"ICANCEL.DEF",SDLK_ESCAPE);
-	bar    = new CGStatusBar(new CPicture(*background, Rect(8, pos.h - 26, pos.w - 16, 19), 8, pos.h - 26));
+	all     = new CButton(Point(146, 416), "ALTARMY.DEF", CGI->generaltexth->zelp[590], [&] { addAll(); }, SDLK_a);
+	convert = new CButton(Point(269, 416), "ALTSACR.DEF", CGI->generaltexth->zelp[591], [&] { makeDeal(); }, SDLK_RETURN);
+	cancel  = new CButton(Point(392, 416), "ICANCEL.DEF", CGI->generaltexth->zelp[592], [&] { close(); },SDLK_ESCAPE);
+	bar     = new CGStatusBar(new CPicture(*background, Rect(8, pos.h - 26, pos.w - 16, 19), 8, pos.h - 26));
 
 	new CLabel(153, 29,FONT_SMALL, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[485]);//holding area
 	new CLabel(153+295, 29, FONT_SMALL, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[486]);//transformer
@@ -1373,8 +1355,7 @@ CUniversityWindow::CUniversityWindow(const CGHeroInstance * _hero, const IMarket
 	for (int i=0; i<list.size(); i++)//prepare clickable items
 		items.push_back(new CItem(this, list[i], 54+i*104, 234));
 
-	cancel = new CAdventureMapButton(CGI->generaltexth->zelp[632],
-		boost::bind(&CUniversityWindow::close, this),200,313,"IOKAY.DEF",SDLK_RETURN);
+	cancel = new CButton(Point(200, 313), "IOKAY.DEF", CGI->generaltexth->zelp[632], [&]{ close(); }, SDLK_RETURN);
 
 	bar = new CGStatusBar(new CPicture(*background, Rect(8, pos.h - 26, pos.w - 16, 19), 8, pos.h - 26));
 }
@@ -1407,12 +1388,10 @@ CUnivConfirmWindow::CUnivConfirmWindow(CUniversityWindow * PARENT, int SKILL, bo
 	boost::replace_first(text, "%s", CGI->generaltexth->skillName[SKILL]);
 	boost::replace_first(text, "%d", "2000");
 
-	confirm= new CAdventureMapButton(hoverText, text, boost::bind(&CUnivConfirmWindow::makeDeal, this, SKILL),
-	         148,299,"IBY6432.DEF",SDLK_RETURN);
+	confirm= new CButton(Point(148, 299), "IBY6432.DEF", CButton::tooltip(hoverText, text), [&]{makeDeal(SKILL);}, SDLK_RETURN);
 	confirm->block(!available);
 
-	cancel = new CAdventureMapButton(CGI->generaltexth->zelp[631],boost::bind(&CUnivConfirmWindow::close, this),
-	         252,299,"ICANCEL.DEF",SDLK_ESCAPE);
+	cancel = new CButton(Point(252,299), "ICANCEL.DEF", CGI->generaltexth->zelp[631], [&]{ close(); }, SDLK_ESCAPE);
 	bar = new CGStatusBar(new CPicture(*background, Rect(8, pos.h - 26, pos.w - 16, 19), 8, pos.h - 26));
 }
 
@@ -1439,21 +1418,21 @@ CHillFortWindow::CHillFortWindow(const CGHeroInstance *visitor, const CGObjectIn
 	currState.resize(slotsCount+1);
 	costs.resize(slotsCount);
 	totalSumm.resize(GameConstants::RESOURCE_QUANTITY);
-	std::vector<std::string> files;
-	files += "APHLF1R.DEF", "APHLF1Y.DEF", "APHLF1G.DEF";
+
 	for (int i=0; i<slotsCount; i++)
 	{
 		currState[i] = getState(SlotID(i));
-		upgrade[i] = new CAdventureMapButton(getTextForSlot(SlotID(i)),"",boost::bind(&CHillFortWindow::makeDeal, this, SlotID(i)),
-		                                    107+i*76, 171, "", SDLK_1+i, &files);
+		upgrade[i] = new CButton(Point(107+i*76, 171), "", CButton::tooltip(getTextForSlot(SlotID(i))), [&]{ makeDeal(SlotID(i));}, SDLK_1+i);
 		upgrade[i]->block(currState[i] == -1);
+		for (auto image : { "APHLF1R.DEF", "APHLF1Y.DEF", "APHLF1G.DEF" })
+			upgrade[i]->addImage(image);
 	}
-	files.clear();
-	files += "APHLF4R.DEF", "APHLF4Y.DEF", "APHLF4G.DEF";
 	currState[slotsCount] = getState(SlotID(slotsCount));
-	upgradeAll = new CAdventureMapButton(CGI->generaltexth->allTexts[432],"",boost::bind(&CHillFortWindow::makeDeal, this, SlotID(slotsCount)),
-	                                    30, 231, "", SDLK_0, &files);
-	quit = new CAdventureMapButton("","",boost::bind(&CHillFortWindow::close, this), 294, 275, "IOKAY.DEF", SDLK_RETURN);
+	upgradeAll = new CButton(Point(30, 231), "", CButton::tooltip(CGI->generaltexth->allTexts[432]), [&]{ makeDeal(SlotID(slotsCount));}, SDLK_0);
+	for (auto image : { "APHLF4R.DEF", "APHLF4Y.DEF", "APHLF4G.DEF" })
+		upgradeAll->addImage(image);
+
+	quit = new CButton(Point(294, 275), "IOKAY.DEF", CButton::tooltip(), boost::bind(&CHillFortWindow::close, this), SDLK_RETURN);
 	bar = new CGStatusBar(new CPicture(*background, Rect(8, pos.h - 26, pos.w - 16, 19), 8, pos.h - 26));
 
 	garr = new CGarrisonInt(108, 60, 18, Point(),background->bg,Point(108,60),hero,nullptr);
@@ -1483,7 +1462,7 @@ void CHillFortWindow::updateGarrisons()
 		currState[i] = newState;
 		upgrade[i]->setIndex(newState);
 		upgrade[i]->block(currState[i] == -1);
-		upgrade[i]->hoverTexts[0] = getTextForSlot(SlotID(i));
+		upgrade[i]->addHoverText(CButton::NORMAL, getTextForSlot(SlotID(i)));
 	}
 
 	int newState = getState(SlotID(slotsCount));
@@ -1614,7 +1593,7 @@ CThievesGuildWindow::CThievesGuildWindow(const CGObjectInstance * _owner):
 	SThievesGuildInfo tgi; //info to be displayed
 	LOCPLINT->cb->getThievesGuildInfo(tgi, owner);
 
-	exitb = new CAdventureMapButton (CGI->generaltexth->allTexts[600], "", boost::bind(&CThievesGuildWindow::close,this), 748, 556, "TPMAGE1", SDLK_RETURN);
+	exitb = new CButton (Point(748, 556), "TPMAGE1", CButton::tooltip(CGI->generaltexth->allTexts[600]), [&]{ close();}, SDLK_RETURN);
 	exitb->assignedKeys.insert(SDLK_ESCAPE);
 	statusBar = new CGStatusBar(3, 555, "TStatBar.bmp", 742);
 
@@ -1800,9 +1779,9 @@ void CObjectListWindow::init(CIntObject * titlePic, std::string _title, std::str
 	title = new CLabel(152, 27, FONT_BIG, CENTER, Colors::YELLOW, _title);
 	descr = new CLabel(145, 133, FONT_SMALL, CENTER, Colors::WHITE, _descr);
 
-	ok = new CAdventureMapButton("","",boost::bind(&CObjectListWindow::elementSelected, this),15,402,"IOKAY.DEF", SDLK_RETURN);
+	ok = new CButton(Point(15, 402), "IOKAY.DEF", CButton::tooltip(), boost::bind(&CObjectListWindow::elementSelected, this), SDLK_RETURN);
 	ok->block(true);
-	exit = new CAdventureMapButton("","",boost::bind(&CGuiHandler::popIntTotally,&GH, this),228,402,"ICANCEL.DEF",SDLK_ESCAPE);
+	exit = new CButton( Point(228, 402), "ICANCEL.DEF", CButton::tooltip(), boost::bind(&CGuiHandler::popIntTotally,&GH, this), SDLK_ESCAPE);
 
 	if (titlePic)
 	{

+ 24 - 24
client/windows/GUIClasses.h

@@ -29,8 +29,8 @@ class CComponentBox;
 class CTextInput;
 class CListBox;
 class CLabelGroup;
-class CHighlightableButton;
-class CHighlightableButtonsGroup;
+class CToggleButton;
+class CToggleGroup;
 class CGStatusBar;
 
 /// Recruitment window where you can recruit creatures
@@ -75,7 +75,7 @@ class CRecruitmentWindow : public CWindowObject
 	std::vector<CCreatureCard *> cards;
 
 	CSlider *slider; //for selecting amount
-	CAdventureMapButton *maxButton, *buyButton, *cancelButton;
+	CButton *maxButton, *buyButton, *cancelButton;
 	//labels for visible values
 	CLabel * title;
 	CLabel * availableValue;
@@ -106,7 +106,7 @@ class CSplitWindow : public CWindowObject
 
 	CSlider *slider;
 	CCreaturePic *animLeft, *animRight; //creature's animation
-	CAdventureMapButton *ok, *cancel;
+	CButton *ok, *cancel;
 
 	CTextInput *leftInput, *rightInput;
 	void setAmountText(std::string text, bool left);
@@ -161,7 +161,7 @@ class CObjectListWindow : public CWindowObject
 
 	CListBox * list;
 	CIntObject * titleImage;//title image (castle gate\town portal picture)
-	CAdventureMapButton *ok, *exit;
+	CButton *ok, *exit;
 
 	std::vector< std::pair<int, std::string> > items;//all items present in list
 
@@ -188,19 +188,19 @@ private:
 	CLabel *title;
 	CLabelGroup *leftGroup;
 	CLabelGroup *rightGroup;
-	CAdventureMapButton *load, *save, *restart, *mainMenu, *quitGame, *backToMap; //load and restart are not used yet
-	CHighlightableButtonsGroup * heroMoveSpeed;
-	CHighlightableButtonsGroup * mapScrollSpeed;
-	CHighlightableButtonsGroup * musicVolume, * effectsVolume;
+	CButton *load, *save, *restart, *mainMenu, *quitGame, *backToMap; //load and restart are not used yet
+	CToggleGroup * heroMoveSpeed;
+	CToggleGroup * mapScrollSpeed;
+	CToggleGroup * musicVolume, * effectsVolume;
 
 	//CHighlightableButton * showPath;
-	CHighlightableButton * showReminder;
-	CHighlightableButton * quickCombat;
-	CHighlightableButton * spellbookAnim;
-	CHighlightableButton * newCreatureWin;
-	CHighlightableButton * fullscreen;
+	CToggleButton * showReminder;
+	CToggleButton * quickCombat;
+	CToggleButton * spellbookAnim;
+	CToggleButton * newCreatureWin;
+	CToggleButton * fullscreen;
 
-	CAdventureMapButton *gameResButton;
+	CButton *gameResButton;
 	CLabel *gameResLabel;
 
 	SettingsListener onFullscreenChanged;
@@ -258,7 +258,7 @@ public:
 	int selected;//0 (left) or 1 (right)
 	int oldSelected;//0 (left) or 1 (right)
 
-	CAdventureMapButton *thiefGuild, *cancel, *recruit;
+	CButton *thiefGuild, *cancel, *recruit;
 	const CGObjectInstance *tavernObj;
 
 	CTavernWindow(const CGObjectInstance *TavernObj); //c-tor
@@ -273,7 +273,7 @@ class CExchangeWindow : public CWindowObject, public CWindowWithGarrison, public
 {
 	CGStatusBar * ourBar; //internal statusbar
 
-	CAdventureMapButton * quit, * questlogButton[2];
+	CButton * quit, * questlogButton[2];
 
 	std::vector<LRClickableAreaWTextComp *> secSkillAreas[2], primSkillAreas;
 
@@ -311,7 +311,7 @@ public:
 	CLabel *woodCost, *goldCost;
 
 	CAnimImage *bgShip;
-	CAdventureMapButton *build, *quit;
+	CButton *build, *quit;
 
 	CGStatusBar * statusBar;
 
@@ -324,7 +324,7 @@ class CPuzzleWindow : public CWindowObject
 private:
 	int3 grailPos;
 
-	CAdventureMapButton * quitb;
+	CButton * quitb;
 
 	std::vector<CPicture * > piecesToRemove;
 	ui8 currentAlpha;
@@ -360,7 +360,7 @@ public:
 	const CGTownInstance *town;//market, town garrison is used if hero == nullptr
 	std::vector<CItem*> items;
 
-	CAdventureMapButton *all, *convert, *cancel;
+	CButton *all, *convert, *cancel;
 	CGStatusBar *bar;
 	void makeDeal();
 	void addAll();
@@ -391,7 +391,7 @@ public:
 	CPicture * green, * yellow, * red;//colored bars near skills
 	std::vector<CItem*> items;
 
-	CAdventureMapButton *cancel;
+	CButton *cancel;
 	CGStatusBar *bar;
 
 	CUniversityWindow(const CGHeroInstance * _hero, const IMarket * _market); //c-tor
@@ -403,7 +403,7 @@ class CUnivConfirmWindow : public CWindowObject
 public:
 	CUniversityWindow * parent;
 	CGStatusBar *bar;
-	CAdventureMapButton *confirm, *cancel;
+	CButton *confirm, *cancel;
 
 	CUnivConfirmWindow(CUniversityWindow * PARENT, int SKILL, bool available); //c-tor
 	void makeDeal(int skill);
@@ -418,7 +418,7 @@ public:
 	CGStatusBar * bar;
 	CDefEssential *resources;
 	CHeroArea *heroPic;//clickable hero image
-	CAdventureMapButton *quit,//closes window
+	CButton *quit,//closes window
 	                   *upgradeAll,//upgrade all creatures
 	                   *upgrade[7];//upgrade single creature
 
@@ -443,7 +443,7 @@ class CThievesGuildWindow : public CWindowObject
 	const CGObjectInstance * owner;
 
 	CGStatusBar * statusBar;
-	CAdventureMapButton * exitb;
+	CButton * exitb;
 	CMinorResDataBar * resdatabar;
 
 public:

+ 9 - 10
client/windows/InfoWindows.cpp

@@ -72,10 +72,10 @@ CSelWindow::CSelWindow(const std::string &Text, PlayerColor player, int charperl
 	ID = askID;
 	for(int i=0;i<Buttons.size();i++)
 	{
-		buttons.push_back(new CAdventureMapButton("","",Buttons[i].second,0,0,Buttons[i].first));
+		buttons.push_back(new CButton(Point(0,0), Buttons[i].first, CButton::tooltip(), Buttons[i].second));
 		if(!i  &&  askID.getNum() >= 0)
-			buttons.back()->callback += boost::bind(&CSelWindow::madeChoice,this);
-		buttons[i]->callback += boost::bind(&CInfoWindow::close,this); //each button will close the window apart from call-defined actions
+			buttons.back()->addCallback(boost::bind(&CSelWindow::madeChoice,this));
+		buttons[i]->addCallback(boost::bind(&CInfoWindow::close,this)); //each button will close the window apart from call-defined actions
 	}
 
 	text = new CTextBox(Text, Rect(0, 0, 250, 100), 0, FONT_MEDIUM, CENTER, Colors::WHITE);
@@ -84,7 +84,7 @@ CSelWindow::CSelWindow(const std::string &Text, PlayerColor player, int charperl
 	buttons.back()->assignedKeys.insert(SDLK_ESCAPE); //last button - reacts on escape
 
 	if(buttons.size() > 1  &&  askID.getNum() >= 0) //cancel button functionality
-		buttons.back()->callback += boost::bind(&CCallback::selectionMade,LOCPLINT->cb.get(),0,askID);
+		buttons.back()->addCallback(boost::bind(&CCallback::selectionMade,LOCPLINT->cb.get(),0,askID));
 
 	for(int i=0;i<comps.size();i++)
 	{
@@ -121,10 +121,9 @@ CInfoWindow::CInfoWindow(std::string Text, PlayerColor player, const TCompsInfo
 	ID = QueryID(-1);
 	for(auto & Button : Buttons)
 	{
-		CAdventureMapButton *button = new CAdventureMapButton("","",boost::bind(&CInfoWindow::close,this),0,0,Button.first);
+		CButton *button = new CButton(Point(0,0), Button.first, CButton::tooltip(), boost::bind(&CInfoWindow::close,this));
 		button->borderColor = Colors::METALLIC_GOLD;
-		button->borderEnabled = true;
-		button->callback.add(Button.second); //each button will close the window apart from call-defined actions
+		button->addCallback(Button.second); //each button will close the window apart from call-defined actions
 		buttons.push_back(button);
 	}
 
@@ -199,9 +198,9 @@ void CInfoWindow::showYesNoDialog(const std::string & text, const std::vector<CC
 	pom.push_back(std::pair<std::string,CFunctionList<void()> >("ICANCEL.DEF",0));
 	CInfoWindow * temp = new CInfoWindow(text, player, components ? *components : std::vector<CComponent*>(), pom, DelComps);
 	for(auto & elem : onYes.funcs)
-		temp->buttons[0]->callback += elem;
+		temp->buttons[0]->addCallback(elem);
 	for(auto & elem : onNo.funcs)
-		temp->buttons[1]->callback += elem;
+		temp->buttons[1]->addCallback(elem);
 
 	GH.pushInt(temp);
 }
@@ -211,7 +210,7 @@ void CInfoWindow::showOkDialog(const std::string & text, const std::vector<CComp
 	std::vector<std::pair<std::string,CFunctionList<void()> > > pom;
 	pom.push_back(std::pair<std::string,CFunctionList<void()> >("IOKAY.DEF",0));
 	CInfoWindow * temp = new CInfoWindow(text, player, *components, pom, delComps);
-	temp->buttons[0]->callback += onOk;
+	temp->buttons[0]->addCallback(onOk);
 
 	GH.pushInt(temp);
 }

+ 2 - 2
client/windows/InfoWindows.h

@@ -14,7 +14,7 @@ class CComponent;
 class CSelectableComponent;
 class CGGarrison;
 class CTextBox;
-class CAdventureMapButton;
+class CButton;
 class CSlider;
 
 /*
@@ -46,7 +46,7 @@ public:
 	typedef std::vector<CComponent*> TCompsInfo;
 	QueryID ID; //for identification
 	CTextBox *text;
-	std::vector<CAdventureMapButton *> buttons;
+	std::vector<CButton *> buttons;
 	std::vector<CComponent*> components;
 	CSlider *slider;
 

+ 7 - 0
config/translate.json

@@ -55,6 +55,13 @@
 		"allOf"  :  "All of the following:",
 		"noneOf" : "None of the following:"
 	},
+	"heroWindow" : {
+		"openCommander" :
+		{
+			"label" : "Open commander window",
+			"help" : "Displays information about commander of this hero"
+		}
+	},
 	"creatureWindow" :
 	{
 		"showBonuses" :