Browse Source

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 years ago
parent
commit
10fc1892a8
37 changed files with 939 additions and 931 deletions
  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" :