| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751 | #include "StdInc.h"#include "Buttons.h"#include "Images.h"#include "TextControls.h"#include "../CMusicHandler.h"#include "../CGameInfo.h"#include "../CPlayerInterface.h"#include "../battle/CBattleInterface.h"#include "../battle/CBattleInterfaceClasses.h"#include "../gui/CAnimation.h"#include "../gui/CGuiHandler.h"#include "../windows/InfoWindows.h"#include "../../lib/CConfigHandler.h"/* * Buttons.cpp, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * * License: GNU General Public License v2.0 or later * Full text of license available in license.txt file, in main folder * */ClickableArea::ClickableArea(CIntObject * object, CFunctionList<void()> callback):	callback(callback),	area(nullptr){	if (object)		pos = object->pos;	setArea(object);}void ClickableArea::addCallback(std::function<void()> callback){	this->callback += callback;}void ClickableArea::setArea(CIntObject * object){	delete area;	addChild(area);	pos.w = object->pos.w;	pos.h = object->pos.h;}void ClickableArea::onClick(){	callback();}void ClickableArea::clickLeft(tribool down, bool previousState){	if (down)		onClick();}void CButton::update(){	if (overlay)	{		if (state == PRESSED)			overlay->moveTo(overlay->pos.centerIn(pos).topLeft() + Point(1,1));		else			overlay->moveTo(overlay->pos.centerIn(pos).topLeft());	}	int newPos = stateToIndex[int(state)];	if (newPos < 0)		newPos = 0;	if (state == HIGHLIGHTED && image->size() < 4)		newPos = image->size()-1;	image->setFrame(newPos);	if (active)		redraw();}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 CButton::addOverlay(CIntObject *newOverlay){	delete overlay;	overlay = newOverlay;	addChild(newOverlay);	overlay->moveTo(overlay->pos.centerIn(pos).topLeft());	update();}void CButton::addImage(std::string filename){	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 CButton::setState(ButtonState newState){	if (state == newState)		return;	state = newState;	update();}CButton::ButtonState CButton::getState(){	return state;}bool CButton::isBlocked(){	return state == BLOCKED;}bool CButton::isHighlighted(){	return state == HIGHLIGHTED;}void CButton::block(bool on){	if(on || state == BLOCKED) //dont change button state if unblock requested, but it's not blocked		setState(on ? BLOCKED : NORMAL);}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;	CIntObject * parent = this->parent;	std::string prefix = "Parent is";	while (parent)	{		logAnim->traceStream() << prefix << typeid(*parent).name() << " at " << parent->pos.x << "x" << parent->pos.y;		parent = parent->parent;		prefix = '\t' + prefix;	}	callback();}void CButton::clickLeft(tribool down, bool previousState){	if(isBlocked())		return;	if (down)	{		if (!soundDisabled)			CCS->soundh->playSound(soundBase::button);		setState(PRESSED);	}	else if(hoverable && hovered)		setState(HIGHLIGHTED);	else		setState(NORMAL);	if (actOnDown && down)	{		onButtonClicked();	}	else if (!actOnDown && previousState && (down==false))	{		onButtonClicked();	}}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 CButton::hover (bool on){	if(hoverable && !isBlocked())	{		if(on)			setState(HIGHLIGHTED);		else			setState(NORMAL);	}	/*if(pressedL && on) // WTF is this? When this is used?		setState(PRESSED);*/	std::string name = hoverTexts[getState()].empty()		? hoverTexts[0]		: hoverTexts[getState()];	if(!name.empty() && !isBlocked()) //if there is no name, there is nothing to display also	{		if (LOCPLINT && LOCPLINT->battleInt) //for battle buttons		{			if(on && LOCPLINT->battleInt->console->alterTxt == "")			{				LOCPLINT->battleInt->console->alterTxt = name;				LOCPLINT->battleInt->console->whoSetAlter = 1;			}			else if (LOCPLINT->battleInt->console->alterTxt == name)			{				LOCPLINT->battleInt->console->alterTxt = "";				LOCPLINT->battleInt->console->whoSetAlter = 0;			}		}		else if(GH.statusbar) //for other buttons		{			if (on)				GH.statusbar->setText(name);			else if ( GH.statusbar->getText()==(name) )				GH.statusbar->clear();		}	}}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){	addUsedEvents(LCLICK | RCLICK | HOVER | KEYBOARD);	stateToIndex[0] = 0;	stateToIndex[1] = 1;	stateToIndex[2] = 2;	stateToIndex[3] = 3;	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);		setIndex(0, playerColoredButton);	}}void CButton::setIndex(size_t index, bool playerColoredButton){	if (index == currentImage || index>=imageNames.size())		return;	currentImage = index;	setImage(new CAnimation(imageNames[index]), playerColoredButton);}void CButton::setImage(CAnimation* anim, bool playerColoredButton, int animFlags){	OBJ_CONSTRUCTION_CAPTURING_ALL;	image = new CAnimImage(anim, getState(), 0, 0, 0, animFlags);	if (playerColoredButton)		image->playerColored(LOCPLINT->playerID);	pos = image->pos;}void CButton::setPlayerColor(PlayerColor player){	if (image)		image->playerColored(player);}void CButton::showAll(SDL_Surface * to){	CIntObject::showAll(to);		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));}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),    selected(false),    allowDeselection(true){}CToggleBase::~CToggleBase(){}void CToggleBase::doSelect(bool on){	// for overrides}void CToggleBase::setSelected(bool on){	bool changed = (on != selected);	selected = on;	doSelect(on);	if (changed)		callback(on);}bool CToggleBase::canActivate(){	if (selected && !allowDeselection)		return false;	return true;}void CToggleBase::addCallback(std::function<void(bool)> function){	callback += 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){	allowDeselection = true;}void CToggleButton::doSelect(bool on){	if (on)	{		setState(HIGHLIGHTED);	}	else	{		setState(NORMAL);	}}void CToggleButton::clickLeft(tribool down, bool previousState){	// force refresh	hover(false);	hover(true);	if(isBlocked())		return;	if (down && canActivate())	{		CCS->soundh->playSound(soundBase::button);		setState(PRESSED);	}	if(previousState)//mouse up	{		if(down == false && getState() == PRESSED && canActivate())		{			onButtonClicked();			setSelected(!selected);		}		else			doSelect(selected); // restore	}}void CToggleGroup::addCallback(std::function<void(int)> callback){	onChange += callback;}void CToggleGroup::addToggle(int identifier, CToggleBase* bt){	if (auto intObj = dynamic_cast<CIntObject*>(bt)) // hack-ish workagound to avoid diamond problem with inheritance	{		if (intObj->parent)			intObj->parent->removeChild(intObj);		addChild(intObj);	}	bt->addCallback([=] (bool on) { if (on) selectionChanged(identifier);});	bt->allowDeselection = false;	assert(buttons[identifier] == nullptr);	buttons[identifier] = bt;}CToggleGroup::CToggleGroup(const CFunctionList<void(int)> &OnChange, bool musicLikeButtons): onChange(OnChange), selectedID(-2), musicLike(musicLikeButtons){}void CToggleGroup::setSelected(int id){	selectionChanged(id);}void CToggleGroup::selectionChanged(int to){	if (to == selectedID)		return;	int oldSelection = selectedID;	selectedID = to;	if (buttons.count(oldSelection))		buttons[oldSelection]->setSelected(false);	if (buttons.count(to))		buttons[to]->setSelected(true);	onChange(to);	if (parent)		parent->redraw();}void CToggleGroup::show(SDL_Surface * to){	if (musicLike)	{		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 CToggleGroup::showAll(SDL_Surface * to){	if (musicLike)	{		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 CSlider::sliderClicked(){	if(!(active & MOVE))		addUsedEvents(MOVE);}void CSlider::mouseMoved (const SDL_MouseMotionEvent & sEvent){	double v = 0;	if(horizontal)	{		if(	std::abs(sEvent.y-(pos.y+pos.h/2)) > pos.h/2+40  ||  std::abs(sEvent.x-(pos.x+pos.w/2)) > pos.w/2  )			return;		v = sEvent.x - pos.x - 24;		v *= positions;		v /= (pos.w - 48);	}	else	{		if(std::abs(sEvent.x-(pos.x+pos.w/2)) > pos.w/2+40  ||  std::abs(sEvent.y-(pos.y+pos.h/2)) > pos.h/2  )			return;		v = sEvent.y - pos.y - 24;		v *= positions;		v /= (pos.h - 48);	}	v += 0.5;	if(v!=value)	{		moveTo(v);	}}void CSlider::setScrollStep(int to){	scrollStep = to;}int CSlider::getAmount(){	return amount;}int CSlider::getValue(){	return value;}void CSlider::moveLeft(){	moveTo(value-1);}void CSlider::moveRight(){	moveTo(value+1);}void CSlider::moveBy(int amount){	moveTo(value + amount);}void CSlider::updateSliderPos(){	if(horizontal)	{		if(positions)		{			double part = static_cast<double>(value) / positions;			part*=(pos.w-48);			int newPos = part + pos.x + 16 - slider->pos.x;			slider->moveBy(Point(newPos, 0));		}		else			slider->moveTo(Point(pos.x+16, pos.y));	}	else	{		if(positions)		{			double part = static_cast<double>(value) / positions;			part*=(pos.h-48);			int newPos = part + pos.y + 16 - slider->pos.y;			slider->moveBy(Point(0, newPos));		}		else			slider->moveTo(Point(pos.x, pos.y+16));	}}void CSlider::moveTo(int to){	vstd::amax(to, 0);	vstd::amin(to, positions);	//same, old position?	if(value == to)		return;	value = to;	updateSliderPos();	moved(to);}void CSlider::clickLeft(tribool down, bool previousState){	if(down && !slider->isBlocked())	{		double pw = 0;		double rw = 0;		if(horizontal)		{			pw = GH.current->motion.x-pos.x-25;			rw = pw / static_cast<double>(pos.w - 48);		}		else		{			pw = GH.current->motion.y-pos.y-24;			rw = pw / (pos.h-48);		}		if(pw < -8  ||  pw > (horizontal ? pos.w : pos.h) - 40)			return;		// 		if (rw>1) return;		// 		if (rw<0) return;		slider->clickLeft(true, slider->pressedL);		moveTo(rw * positions  +  0.5);		return;	}	if(active & MOVE)		removeUsedEvents(MOVE);}CSlider::~CSlider(){}CSlider::CSlider(Point position, int totalw, std::function<void(int)> Moved, int Capacity, int Amount, int Value, bool Horizontal, CSlider::EStyle style):    capacity(Capacity),    horizontal(Horizontal),    amount(Amount),    value(Value),    scrollStep(1),    moved(Moved){	OBJ_CONSTRUCTION_CAPTURING_ALL;	setAmount(amount);	vstd::amax(value, 0);	vstd::amin(value, positions);	addUsedEvents(LCLICK | KEYBOARD | WHEEL);	strongInterest = true;	pos.x += position.x;	pos.y += position.y;	if(style == BROWN)	{		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...)		left =   new CButton(Point(), name, CButton::tooltip());		right =  new CButton(Point(), name, CButton::tooltip());		slider = new CButton(Point(), name, CButton::tooltip());		left->setImageOrder(0, 1, 1, 1);		right->setImageOrder(2, 3, 3, 3);		slider->setImageOrder(4, 4, 4, 4);	}	else	{		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(std::bind(&CSlider::moveLeft,this));	right->addCallback(std::bind(&CSlider::moveRight,this));	slider->addCallback(std::bind(&CSlider::sliderClicked,this));	if(horizontal)	{		pos.h = slider->pos.h;		pos.w = totalw;	}	else	{		pos.w = slider->pos.w;		pos.h = totalw;	}	updateSliderPos();}void CSlider::block( bool on ){	left->block(on);	right->block(on);	slider->block(on);}void CSlider::setAmount( int to ){	amount = to;	positions = to - capacity;	vstd::amax(positions, 0);}void CSlider::showAll(SDL_Surface * to){	CSDL_Ext::fillRectBlack(to, &pos);	CIntObject::showAll(to);}void CSlider::wheelScrolled(bool down, bool in){	moveTo(value + 3 * (down ? +scrollStep : -scrollStep));}void CSlider::keyPressed(const SDL_KeyboardEvent & key){	if(key.state != SDL_PRESSED) return;	int moveDest = value;	switch(key.keysym.sym)	{	case SDLK_UP:		if (!horizontal)			moveDest = value - scrollStep;		break;	case SDLK_LEFT:		if (horizontal)			moveDest = value - scrollStep;		break;	case SDLK_DOWN:		if (!horizontal)			moveDest = value + scrollStep;		break;	case SDLK_RIGHT:		if (horizontal)			moveDest = value + scrollStep;		break;	case SDLK_PAGEUP:		moveDest = value - capacity + scrollStep;		break;	case SDLK_PAGEDOWN:		moveDest = value + capacity - scrollStep;		break;	case SDLK_HOME:		moveDest = 0;		break;	case SDLK_END:		moveDest = amount - capacity;		break;	default:		return;	}	moveTo(moveDest);}void CSlider::moveToMin(){	moveTo(0);}void CSlider::moveToMax(){	moveTo(amount);}
 |