浏览代码

Merge pull request #2168 from IvanSavenko/gui_handler_event_handling_refactoring

Event handling refactoring
Ivan Savenko 2 年之前
父节点
当前提交
2c3e8c3390
共有 59 个文件被更改,包括 1645 次插入1148 次删除
  1. 7 167
      client/CMT.cpp
  2. 21 3
      client/CMakeLists.txt
  3. 7 32
      client/CPlayerInterface.cpp
  4. 0 1
      client/CPlayerInterface.h
  5. 1 1
      client/CServerHandler.cpp
  6. 8 18
      client/CVideoHandler.cpp
  7. 0 39
      client/ClientCommandManager.cpp
  8. 0 4
      client/ClientCommandManager.h
  9. 8 8
      client/adventureMap/AdventureMapInterface.cpp
  10. 1 1
      client/adventureMap/CInGameConsole.cpp
  11. 1 2
      client/adventureMap/CInfoBar.cpp
  12. 3 2
      client/adventureMap/CMinimap.cpp
  13. 1 1
      client/battle/BattleFieldController.cpp
  14. 2 1
      client/battle/BattleInterfaceClasses.cpp
  15. 1 1
      client/battle/BattleStacksController.cpp
  16. 269 0
      client/eventsSDL/InputHandler.cpp
  17. 77 0
      client/eventsSDL/InputHandler.h
  18. 85 0
      client/eventsSDL/InputSourceKeyboard.cpp
  19. 23 0
      client/eventsSDL/InputSourceKeyboard.h
  20. 72 0
      client/eventsSDL/InputSourceMouse.cpp
  21. 25 0
      client/eventsSDL/InputSourceMouse.h
  22. 92 0
      client/eventsSDL/InputSourceText.cpp
  23. 29 0
      client/eventsSDL/InputSourceText.h
  24. 135 0
      client/eventsSDL/InputSourceTouch.cpp
  25. 37 0
      client/eventsSDL/InputSourceTouch.h
  26. 2 2
      client/eventsSDL/NotificationHandler.cpp
  27. 0 0
      client/eventsSDL/NotificationHandler.h
  28. 87 0
      client/eventsSDL/UserEventHandler.cpp
  29. 20 0
      client/eventsSDL/UserEventHandler.h
  30. 32 540
      client/gui/CGuiHandler.cpp
  31. 17 68
      client/gui/CGuiHandler.h
  32. 34 90
      client/gui/CIntObject.cpp
  33. 26 96
      client/gui/CIntObject.h
  34. 1 1
      client/gui/CursorHandler.cpp
  35. 244 0
      client/gui/EventDispatcher.cpp
  36. 68 0
      client/gui/EventDispatcher.h
  37. 74 0
      client/gui/EventsReceiver.cpp
  38. 77 0
      client/gui/EventsReceiver.h
  39. 1 1
      client/gui/InterfaceObjectConfigurable.cpp
  40. 1 5
      client/gui/WindowHandler.cpp
  41. 1 1
      client/lobby/CSelectionBase.cpp
  42. 4 13
      client/lobby/RandomMapTab.cpp
  43. 0 1
      client/lobby/RandomMapTab.h
  44. 2 2
      client/lobby/SelectionTab.cpp
  45. 1 1
      client/mainmenu/CCampaignScreen.cpp
  46. 1 2
      client/mainmenu/CMainMenu.cpp
  47. 3 2
      client/mapView/MapViewActions.cpp
  48. 1 1
      client/renderSDL/ScreenHandler.cpp
  49. 26 21
      client/widgets/Buttons.cpp
  50. 1 1
      client/widgets/CArtifactHolder.cpp
  51. 1 1
      client/widgets/CArtifactsOfHeroBase.cpp
  52. 1 1
      client/widgets/CWindowWithArtifacts.cpp
  53. 3 3
      client/widgets/ObjectLists.cpp
  54. 5 3
      client/widgets/TextControls.cpp
  55. 3 6
      client/windows/CCastleInterface.cpp
  56. 1 1
      client/windows/CKingdomInterface.cpp
  57. 0 2
      client/windows/CSpellWindow.cpp
  58. 1 1
      client/windows/CTradeWindow.cpp
  59. 1 1
      client/windows/GUIClasses.cpp

+ 7 - 167
client/CMT.cpp

@@ -14,36 +14,29 @@
 
 #include "CGameInfo.h"
 #include "mainmenu/CMainMenu.h"
-#include "mainmenu/CPrologEpilogVideo.h"
 #include "gui/CursorHandler.h"
+#include "eventsSDL/InputHandler.h"
 #include "CPlayerInterface.h"
 #include "CVideoHandler.h"
 #include "CMusicHandler.h"
 #include "gui/CGuiHandler.h"
 #include "gui/WindowHandler.h"
 #include "CServerHandler.h"
-#include "gui/NotificationHandler.h"
 #include "ClientCommandManager.h"
 #include "windows/CMessage.h"
 #include "render/IScreenHandler.h"
 
 #include "../lib/filesystem/Filesystem.h"
-#include "../lib/CConsoleHandler.h"
 #include "../lib/CGeneralTextHandler.h"
 #include "../lib/VCMIDirs.h"
-#include "../lib/mapping/CCampaignHandler.h"
+#include "../lib/CConfigHandler.h"
 
 #include "../lib/logging/CBasicLogConfigurator.h"
 
 #include <boost/program_options.hpp>
 #include <vstd/StringUtils.h>
-#include <SDL_events.h>
-#include <SDL_hints.h>
-#include <SDL_main.h>
 
-#ifdef VCMI_WINDOWS
-#include <SDL_syswm.h>
-#endif
+#include <SDL_main.h>
 
 #ifdef VCMI_ANDROID
 #include "../lib/CAndroidVMHelper.h"
@@ -60,9 +53,6 @@ namespace bfs = boost::filesystem;
 
 extern boost::thread_specific_ptr<bool> inGuiThread;
 
-std::queue<SDL_Event> SDLEventsQueue;
-boost::mutex eventsM;
-
 static po::variables_map vm;
 
 #ifndef VCMI_IOS
@@ -325,19 +315,6 @@ int main(int argc, char * argv[])
 		logGlobal->info("Initializing screen and sound handling: %d ms", pomtime.getDiff());
 	}
 
-#ifdef VCMI_MAC
-	// Ctrl+click should be treated as a right click on Mac OS X
-	SDL_SetHint(SDL_HINT_MAC_CTRL_CLICK_EMULATE_RIGHT_CLICK, "1");
-#endif
-
-#ifdef SDL_HINT_MOUSE_TOUCH_EVENTS
-	if(GH.isPointerRelativeMode)
-	{
-		SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "0");
-		SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0");
-	}
-#endif
-
 #ifndef VCMI_NO_THREADED_LOAD
 	//we can properly play intro only in the main thread, so we have to move loading to the separate thread
 	boost::thread loading(init);
@@ -469,147 +446,18 @@ void playIntro()
 	}
 }
 
-static void handleEvent(SDL_Event & ev)
-{
-	if((ev.type==SDL_QUIT) ||(ev.type == SDL_KEYDOWN && ev.key.keysym.sym==SDLK_F4 && (ev.key.keysym.mod & KMOD_ALT)))
-	{
-#ifdef VCMI_ANDROID
-		handleQuit(false);
-#else
-		handleQuit();
-#endif
-		return;
-	}
-#ifdef VCMI_ANDROID
-	else if (ev.type == SDL_KEYDOWN && ev.key.keysym.scancode == SDL_SCANCODE_AC_BACK)
-	{
-		handleQuit(true);
-	}
-#endif
-	else if(ev.type == SDL_KEYDOWN && ev.key.keysym.sym==SDLK_F4)
-	{
-		Settings full = settings.write["video"]["fullscreen"];
-		full->Bool() = !full->Bool();
-		return;
-	}
-	else if(ev.type == SDL_USEREVENT)
-	{
-		switch(static_cast<EUserEvent>(ev.user.code))
-		{
-		case EUserEvent::FORCE_QUIT:
-			{
-				handleQuit(false);
-				return;
-			}
-			break;
-		case EUserEvent::RETURN_TO_MAIN_MENU:
-			{
-				CSH->endGameplay();
-				GH.defActionsDef = 63;
-				CMM->menu->switchToTab("main");
-			}
-			break;
-		case EUserEvent::RESTART_GAME:
-			{
-				CSH->sendRestartGame();
-			}
-			break;
-		case EUserEvent::CAMPAIGN_START_SCENARIO:
-			{
-				CSH->campaignServerRestartLock.set(true);
-				CSH->endGameplay();
-				auto ourCampaign = std::shared_ptr<CCampaignState>(reinterpret_cast<CCampaignState *>(ev.user.data1));
-				auto & epilogue = ourCampaign->camp->scenarios[ourCampaign->mapsConquered.back()].epilog;
-				auto finisher = [=]()
-				{
-					if(ourCampaign->mapsRemaining.size())
-					{
-						GH.windows().pushWindow(CMM);
-						GH.windows().pushWindow(CMM->menu);
-						CMM->openCampaignLobby(ourCampaign);
-					}
-				};
-				if(epilogue.hasPrologEpilog)
-				{
-					GH.windows().createAndPushWindow<CPrologEpilogVideo>(epilogue, finisher);
-				}
-				else
-				{
-					CSH->campaignServerRestartLock.waitUntil(false);
-					finisher();
-				}
-			}
-			break;
-		case EUserEvent::RETURN_TO_MENU_LOAD:
-			CSH->endGameplay();
-			GH.defActionsDef = 63;
-			CMM->menu->switchToTab("load");
-			break;
-		case EUserEvent::FULLSCREEN_TOGGLED:
-			{
-				boost::unique_lock<boost::recursive_mutex> lock(*CPlayerInterface::pim);
-				GH.onScreenResize();
-				break;
-			}
-		default:
-			logGlobal->error("Unknown user event. Code %d", ev.user.code);
-			break;
-		}
-
-		return;
-	}
-	else if(ev.type == SDL_WINDOWEVENT)
-	{
-		switch (ev.window.event) {
-		case SDL_WINDOWEVENT_RESTORED:
-#ifndef VCMI_IOS
-			{
-				boost::unique_lock<boost::recursive_mutex> lock(*CPlayerInterface::pim);
-				GH.onScreenResize();
-			}
-#endif
-			break;
-		}
-		return;
-	}
-	else if(ev.type == SDL_SYSWMEVENT)
-	{
-		if(!settings["session"]["headless"].Bool() && settings["general"]["notifications"].Bool())
-		{
-			NotificationHandler::handleSdlEvent(ev);
-		}
-	}
-
-	//preprocessing
-	if(ev.type == SDL_MOUSEMOTION)
-	{
-		CCS->curh->cursorMove(ev.motion.x, ev.motion.y);
-	}
-
-	{
-		boost::unique_lock<boost::mutex> lock(eventsM);
-		SDLEventsQueue.push(ev);
-	}
-}
-
 static void mainLoop()
 {
 	SettingsListener resChanged = settings.listen["video"]["resolution"];
 	SettingsListener fsChanged = settings.listen["video"]["fullscreen"];
-	resChanged([](const JsonNode &newState){  CGuiHandler::pushUserEvent(EUserEvent::FULLSCREEN_TOGGLED); });
-	fsChanged([](const JsonNode &newState){  CGuiHandler::pushUserEvent(EUserEvent::FULLSCREEN_TOGGLED); });
+	resChanged([](const JsonNode &newState){  GH.pushUserEvent(EUserEvent::FULLSCREEN_TOGGLED); });
+	fsChanged([](const JsonNode &newState){  GH.pushUserEvent(EUserEvent::FULLSCREEN_TOGGLED); });
 
 	inGuiThread.reset(new bool(true));
 
 	while(1) //main SDL events loop
 	{
-		SDL_Event ev;
-
-		while(1 == SDL_PollEvent(&ev))
-		{
-			handleEvent(ev);
-		}
-
+		GH.input().fetchEvents();
 		CSH->applyPacksOnLobbyScreen();
 		GH.renderFrame();
 	}
@@ -667,15 +515,7 @@ void handleQuit(bool ask)
 	if(CSH->client && LOCPLINT && ask)
 	{
 		CCS->curh->set(Cursor::Map::POINTER);
-		LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[69], [](){
-			// Workaround for assertion failure on exit:
-			// handleQuit() is alway called during SDL event processing
-			// during which, eventsM is kept locked
-			// this leads to assertion failure if boost::mutex is in locked state
-			eventsM.unlock();
-
-			quitApplication();
-		}, nullptr);
+		LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[69], quitApplication, nullptr);
 	}
 	else
 	{

+ 21 - 3
client/CMakeLists.txt

@@ -27,12 +27,21 @@ set(client_SRCS
 	battle/BattleWindow.cpp
 	battle/CreatureAnimation.cpp
 
+	eventsSDL/NotificationHandler.cpp
+	eventsSDL/InputHandler.cpp
+	eventsSDL/UserEventHandler.cpp
+	eventsSDL/InputSourceKeyboard.cpp
+	eventsSDL/InputSourceMouse.cpp
+	eventsSDL/InputSourceText.cpp
+	eventsSDL/InputSourceTouch.cpp
+
 	gui/CGuiHandler.cpp
 	gui/CIntObject.cpp
 	gui/CursorHandler.cpp
+	gui/EventDispatcher.cpp
+	gui/EventsReceiver.cpp
 	gui/InterfaceObjectConfigurable.cpp
 	gui/FramerateManager.cpp
-	gui/NotificationHandler.cpp
 	gui/ShortcutHandler.cpp
 	gui/WindowHandler.cpp
 
@@ -160,13 +169,22 @@ set(client_HEADERS
 	battle/BattleWindow.h
 	battle/CreatureAnimation.h
 
+	eventsSDL/NotificationHandler.h
+	eventsSDL/InputHandler.h
+	eventsSDL/UserEventHandler.h
+	eventsSDL/InputSourceKeyboard.h
+	eventsSDL/InputSourceMouse.h
+	eventsSDL/InputSourceText.h
+	eventsSDL/InputSourceTouch.h
+
 	gui/CGuiHandler.h
 	gui/CIntObject.h
 	gui/CursorHandler.h
+	gui/EventDispatcher.h
+	gui/EventsReceiver.h
 	gui/InterfaceObjectConfigurable.h
 	gui/FramerateManager.h
 	gui/MouseButton.h
-	gui/NotificationHandler.h
 	gui/Shortcut.h
 	gui/ShortcutHandler.h
 	gui/TextAlignment.h
@@ -333,7 +351,7 @@ if(WIN32)
 	endif()
 	target_compile_definitions(vcmiclient PRIVATE WINDOWS_IGNORE_PACKING_MISMATCH)
 
-	# TODO: very hacky, find proper solution to copy AI dlls into bin dir
+# TODO: very hacky, find proper solution to copy AI dlls into bin dir
 	if(MSVC)
 		add_custom_command(TARGET vcmiclient POST_BUILD
 			WORKING_DIRECTORY "$<TARGET_FILE_DIR:vcmiclient>"

+ 7 - 32
client/CPlayerInterface.cpp

@@ -22,6 +22,7 @@
 #include "battle/BattleWindow.h"
 #include "../CCallback.h"
 #include "windows/CCastleInterface.h"
+#include "eventsSDL/InputHandler.h"
 #include "gui/CursorHandler.h"
 #include "windows/CKingdomInterface.h"
 #include "CGameInfo.h"
@@ -74,11 +75,9 @@
 #include "CServerHandler.h"
 // FIXME: only needed for CGameState::mutex
 #include "../lib/CGameState.h"
-#include "gui/NotificationHandler.h"
+#include "eventsSDL/NotificationHandler.h"
 #include "adventureMap/CInGameConsole.h"
 
-#include <SDL_events.h>
-
 // The macro below is used to mark functions that are called by client when game state changes.
 // They all assume that CPlayerInterface::pim mutex is locked.
 #define EVENT_HANDLER_CALLED_BY_CLIENT
@@ -96,8 +95,6 @@
 		return;						\
 	RETURN_IF_QUICK_COMBAT
 
-extern std::queue<SDL_Event> SDLEventsQueue;
-extern boost::mutex eventsM;
 boost::recursive_mutex * CPlayerInterface::pim = new boost::recursive_mutex;
 
 CPlayerInterface * LOCPLINT;
@@ -206,7 +203,7 @@ void CPlayerInterface::performAutosave()
 	}
 	else if(frequency > 0 && cb->getDate() % frequency == 0)
 	{
-		LOCPLINT->cb->save("Saves/" + prefix + "Autosave_" + std::to_string(autosaveCount++ + 1));
+		cb->save("Saves/" + prefix + "Autosave_" + std::to_string(autosaveCount++ + 1));
 		autosaveCount %= 5;
 	}
 }
@@ -215,8 +212,6 @@ void CPlayerInterface::yourTurn()
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	{
-		boost::unique_lock<boost::mutex> lock(eventsM); //block handling events until interface is ready
-
 		LOCPLINT = this;
 		GH.curInt = this;
 
@@ -372,22 +367,8 @@ void CPlayerInterface::heroMoved(const TryMoveHero & details, bool verbose)
 
 	//check if user cancelled movement
 	{
-		boost::unique_lock<boost::mutex> un(eventsM);
-		while(!SDLEventsQueue.empty())
-		{
-			SDL_Event ev = SDLEventsQueue.front();
-			SDLEventsQueue.pop();
-			switch(ev.type)
-			{
-			case SDL_MOUSEBUTTONDOWN:
-				stillMoveHero.setn(STOP_MOVE);
-				break;
-			case SDL_KEYDOWN:
-				if (ev.key.keysym.sym < SDLK_F1  ||  ev.key.keysym.sym > SDLK_F15)
-					stillMoveHero.setn(STOP_MOVE);
-				break;
-			}
-		}
+		if (GH.input().ignoreEventsUntilInput())
+			stillMoveHero.setn(STOP_MOVE);
 	}
 
 	if (stillMoveHero.get() == WAITING_MOVE)
@@ -1478,7 +1459,6 @@ void CPlayerInterface::playerBlocked(int reason, bool start)
 		if(CSH->howManyPlayerInterfaces() > 1 && LOCPLINT != this && LOCPLINT->makingTurn == false)
 		{
 			//one of our players who isn't last in order got attacked not by our another player (happens for example in hotseat mode)
-			boost::unique_lock<boost::mutex> lock(eventsM); //TODO: copied from yourTurn, no idea if it's needed
 			LOCPLINT = this;
 			GH.curInt = this;
 			adventureInt->onCurrentPlayerChanged(playerID);
@@ -1513,7 +1493,6 @@ void CPlayerInterface::update()
 	assert(adventureInt);
 
 	// Handles mouse and key input
-	GH.updateTime();
 	GH.handleEvents();
 	GH.windows().simpleRedraw();
 }
@@ -1872,15 +1851,11 @@ bool CPlayerInterface::capturedAllEvents()
 		return true;
 	}
 
-	bool needToLockAdventureMap = adventureInt && adventureInt->active && CGI->mh->hasOngoingAnimations();
+	bool needToLockAdventureMap = adventureInt && adventureInt->isActive() && CGI->mh->hasOngoingAnimations();
 
 	if (ignoreEvents || needToLockAdventureMap || isAutoFightOn)
 	{
-		boost::unique_lock<boost::mutex> un(eventsM);
-		while(!SDLEventsQueue.empty())
-		{
-			SDLEventsQueue.pop();
-		}
+		GH.input().ignoreEventsUntilInput();
 		return true;
 	}
 

+ 0 - 1
client/CPlayerInterface.h

@@ -47,7 +47,6 @@ class KeyInterested;
 class MotionInterested;
 class PlayerLocalState;
 class TimeInterested;
-class IShowable;
 
 namespace boost
 {

+ 1 - 1
client/CServerHandler.cpp

@@ -843,7 +843,7 @@ void CServerHandler::threadHandleConnection()
 			if(client)
 			{
 				state = EClientState::DISCONNECTING;
-				CGuiHandler::pushUserEvent(EUserEvent::RETURN_TO_MAIN_MENU);
+				GH.pushUserEvent(EUserEvent::RETURN_TO_MAIN_MENU);
 			}
 			else
 			{

+ 8 - 18
client/CVideoHandler.cpp

@@ -12,15 +12,13 @@
 
 #include "CMT.h"
 #include "gui/CGuiHandler.h"
+#include "eventsSDL/InputHandler.h"
 #include "gui/FramerateManager.h"
 #include "renderSDL/SDL_Extensions.h"
 #include "CPlayerInterface.h"
 #include "../lib/filesystem/Filesystem.h"
 
 #include <SDL_render.h>
-#include <SDL_events.h>
-
-extern CGuiHandler GH; //global gui handler
 
 #ifndef DISABLE_VIDEO
 
@@ -31,18 +29,6 @@ extern "C" {
 #include <libswscale/swscale.h>
 }
 
-//reads events and returns true on key down
-static bool keyDown()
-{
-	SDL_Event ev;
-	while(SDL_PollEvent(&ev))
-	{
-		if(ev.type == SDL_KEYDOWN || ev.type == SDL_MOUSEBUTTONDOWN)
-			return true;
-	}
-	return false;
-}
-
 #ifdef _MSC_VER
 #pragma comment(lib, "avcodec.lib")
 #pragma comment(lib, "avutil.lib")
@@ -371,7 +357,7 @@ void CVideoPlayer::update( int x, int y, SDL_Surface *dst, bool forceRedraw, boo
 	auto packet_duration = frame->duration;
 #endif
 	double frameEndTime = (frame->pts + packet_duration) * av_q2d(format->streams[stream]->time_base);
-	frameTime += GH.framerateManager().getElapsedMilliseconds() / 1000.0;
+	frameTime += GH.framerate().getElapsedMilliseconds() / 1000.0;
 
 	if (frameTime >= frameEndTime )
 	{
@@ -455,8 +441,12 @@ bool CVideoPlayer::playVideo(int x, int y, bool stopOnKey)
 
 	while(nextFrame())
 	{
-		if(stopOnKey && keyDown())
-			return false;
+		if(stopOnKey)
+		{
+			GH.input().fetchEvents();
+			if(GH.input().ignoreEventsUntilInput())
+				return false;
+		}
 
 		SDL_Rect rect = CSDL_Ext::toSDL(pos);
 

+ 0 - 39
client/ClientCommandManager.cpp

@@ -194,12 +194,6 @@ void ClientCommandManager::handleNotDialogCommand()
 	LOCPLINT->showingDialog->setn(false);
 }
 
-void ClientCommandManager::handleGuiCommand()
-{
-	for(const auto & child : GH.windows().findWindows<CIntObject>())
-		printInfoAboutInterfaceObject(child.get(), 0);
-}
-
 void ClientCommandManager::handleConvertTextCommand()
 {
 	logGlobal->info("Searching for available maps");
@@ -487,36 +481,6 @@ void ClientCommandManager::printCommandMessage(const std::string &commandMessage
 	}
 }
 
-void ClientCommandManager::printInfoAboutInterfaceObject(const CIntObject *obj, int level)
-{
-	std::stringstream sbuffer;
-	sbuffer << std::string(level, '\t');
-
-	sbuffer << typeid(*obj).name() << " *** ";
-	if (obj->active)
-	{
-#define PRINT(check, text) if (obj->active & CIntObject::check) sbuffer << text
-		PRINT(LCLICK, 'L');
-		PRINT(RCLICK, 'R');
-		PRINT(HOVER, 'H');
-		PRINT(MOVE, 'M');
-		PRINT(KEYBOARD, 'K');
-		PRINT(TIME, 'T');
-		PRINT(GENERAL, 'A');
-		PRINT(WHEEL, 'W');
-		PRINT(DOUBLECLICK, 'D');
-#undef  PRINT
-	}
-	else
-		sbuffer << "inactive";
-	sbuffer << " at " << obj->pos.x <<"x"<< obj->pos.y;
-	sbuffer << " (" << obj->pos.w <<"x"<< obj->pos.h << ")";
-	printCommandMessage(sbuffer.str(), ELogLevel::INFO);
-
-	for(const CIntObject *child : obj->children)
-		printInfoAboutInterfaceObject(child, level+1);
-}
-
 void ClientCommandManager::giveTurn(const PlayerColor &colorIdentifier)
 {
 	YourTurn yt;
@@ -569,9 +533,6 @@ void ClientCommandManager::processCommand(const std::string & message, bool call
 	else if(commandName == "not dialog")
 		handleNotDialogCommand();
 
-	else if(commandName == "gui")
-		handleGuiCommand();
-
 	else if(message=="convert txt")
 		handleConvertTextCommand();
 

+ 0 - 4
client/ClientCommandManager.h

@@ -51,9 +51,6 @@ class ClientCommandManager //take mantis #2292 issue about account if thinking a
 	// Set the state indicating if dialog box is active to "no"
 	void handleNotDialogCommand();
 
-	// Displays tree view of currently present VCMI common GUI elements
-	void handleGuiCommand();
-
 	// Dumps all game text, maps text and campaign maps text into Client log between BEGIN TEXT EXPORT and END TEXT EXPORT
 	void handleConvertTextCommand();
 
@@ -92,7 +89,6 @@ class ClientCommandManager //take mantis #2292 issue about account if thinking a
 
 	// Prints in Chat the given message
 	void printCommandMessage(const std::string &commandMessage, ELogLevel::ELogLevel messageType = ELogLevel::NOT_SET);
-	void printInfoAboutInterfaceObject(const CIntObject *obj, int level);
 	void giveTurn(const PlayerColor &color);
 
 public:

+ 8 - 8
client/adventureMap/AdventureMapInterface.cpp

@@ -52,7 +52,7 @@ AdventureMapInterface::AdventureMapInterface():
 	pos.x = pos.y = 0;
 	pos.w = GH.screenDimensions().x;
 	pos.h = GH.screenDimensions().y;
-	strongInterest = true; // handle all mouse move events to prevent dead mouse move space in fullscreen mode
+	setMoveEventStrongInterest(true); // handle all mouse move events to prevent dead mouse move space in fullscreen mode
 
 	shortcuts = std::make_shared<AdventureMapShortcuts>(*this);
 
@@ -179,7 +179,7 @@ void AdventureMapInterface::handleMapScrollingUpdate(uint32_t timePassed)
 	Point scrollDelta = scrollDirection * scrollDistance;
 
 	bool cursorInScrollArea = scrollDelta != Point(0,0);
-	bool scrollingActive = cursorInScrollArea && active && shortcuts->optionSidePanelActive() && !scrollingWasBlocked;
+	bool scrollingActive = cursorInScrollArea && isActive() && shortcuts->optionSidePanelActive() && !scrollingWasBlocked;
 	bool scrollingBlocked = GH.isKeyboardCtrlDown();
 
 	if (!scrollingWasActive && scrollingBlocked)
@@ -323,19 +323,19 @@ void AdventureMapInterface::setState(EAdventureState state)
 
 void AdventureMapInterface::adjustActiveness()
 {
-	bool widgetMustBeActive = active && shortcuts->optionSidePanelActive();
-	bool mapViewMustBeActive = active && (shortcuts->optionMapViewActive());
+	bool widgetMustBeActive = isActive() && shortcuts->optionSidePanelActive();
+	bool mapViewMustBeActive = isActive() && (shortcuts->optionMapViewActive());
 
-	if (widgetMustBeActive && !widget->active)
+	if (widgetMustBeActive && !widget->isActive())
 		widget->activate();
 
-	if (!widgetMustBeActive && widget->active)
+	if (!widgetMustBeActive && widget->isActive())
 		widget->deactivate();
 
-	if (mapViewMustBeActive && !widget->getMapView()->active)
+	if (mapViewMustBeActive && !widget->getMapView()->isActive())
 		widget->getMapView()->activate();
 
-	if (!mapViewMustBeActive && widget->getMapView()->active)
+	if (!mapViewMustBeActive && widget->getMapView()->isActive())
 		widget->getMapView()->deactivate();
 }
 

+ 1 - 1
client/adventureMap/CInGameConsole.cpp

@@ -211,7 +211,7 @@ void CInGameConsole::textEdited(const std::string & inputtedText)
 
 void CInGameConsole::startEnteringText()
 {
-	if (!active)
+	if (!isActive())
 		return;
 
 	if (captureAllKeys)

+ 1 - 2
client/adventureMap/CInfoBar.cpp

@@ -316,8 +316,7 @@ CInfoBar::CInfoBar(const Point & position): CInfoBar(Rect(position.x, position.y
 
 void CInfoBar::setTimer(uint32_t msToTrigger)
 {
-	if (!(active & TIME))
-		addUsedEvents(TIME);
+	addUsedEvents(TIME);
 	timerCounter = msToTrigger;
 }
 

+ 3 - 2
client/adventureMap/CMinimap.cpp

@@ -17,6 +17,7 @@
 #include "../CGameInfo.h"
 #include "../CPlayerInterface.h"
 #include "../gui/CGuiHandler.h"
+#include "../gui/MouseButton.h"
 #include "../gui/WindowHandler.h"
 #include "../render/Colors.h"
 #include "../renderSDL/SDL_Extensions.h"
@@ -131,7 +132,7 @@ void CMinimap::moveAdvMapSelection()
 	int3 newLocation = pixelToTile(GH.getCursorPosition() - pos.topLeft());
 	adventureInt->centerOnTile(newLocation);
 
-	if (!(adventureInt->active & GENERAL))
+	if (!(adventureInt->isActive()))
 		GH.windows().totalRedraw(); //redraw this as well as inactive adventure map
 	else
 		redraw();//redraw only this
@@ -159,7 +160,7 @@ void CMinimap::hover(bool on)
 
 void CMinimap::mouseMoved(const Point & cursorPosition)
 {
-	if(mouseState(MouseButton::LEFT))
+	if(isMouseButtonPressed(MouseButton::LEFT))
 		moveAdvMapSelection();
 }
 

+ 1 - 1
client/battle/BattleFieldController.cpp

@@ -39,7 +39,7 @@ BattleFieldController::BattleFieldController(BattleInterface & owner):
 	owner(owner)
 {
 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
-	strongInterest = true;
+	setMoveEventStrongInterest(true);
 
 	//preparing cells and hexes
 	cellBorder = IImage::createFromFile("CCELLGRD.BMP", EImageBlitMode::COLORKEY);

+ 2 - 1
client/battle/BattleInterfaceClasses.cpp

@@ -25,6 +25,7 @@
 #include "../gui/CursorHandler.h"
 #include "../gui/CGuiHandler.h"
 #include "../gui/Shortcut.h"
+#include "../gui/MouseButton.h"
 #include "../gui/WindowHandler.h"
 #include "../render/Canvas.h"
 #include "../render/IImage.h"
@@ -692,7 +693,7 @@ std::optional<uint32_t> StackQueue::getHoveredUnitIdIfAny() const
 {
 	for(const auto & stackBox : stackBoxes)
 	{
-		if(stackBox->hovered || stackBox->mouseState(MouseButton::RIGHT))
+		if(stackBox->isHovered() || stackBox->isMouseButtonPressed(MouseButton::RIGHT))
 		{
 			return stackBox->getBoundUnitID();
 		}

+ 1 - 1
client/battle/BattleStacksController.cpp

@@ -353,7 +353,7 @@ void BattleStacksController::initializeBattleAnimations()
 
 void BattleStacksController::tickFrameBattleAnimations(uint32_t msPassed)
 {
-	for (auto stack : owner.curInt->cb->battleGetAllStacks(false))
+	for (auto stack : owner.curInt->cb->battleGetAllStacks(true))
 	{
 		if (stackAnimation.find(stack->unitId()) == stackAnimation.end()) //e.g. for summoned but not yet handled stacks
 			continue;

+ 269 - 0
client/eventsSDL/InputHandler.cpp

@@ -0,0 +1,269 @@
+/*
+* InputHandler.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
+*
+*/
+
+#include "StdInc.h"
+#include "InputHandler.h"
+
+#include "NotificationHandler.h"
+#include "InputSourceMouse.h"
+#include "InputSourceKeyboard.h"
+#include "InputSourceTouch.h"
+#include "InputSourceText.h"
+#include "UserEventHandler.h"
+
+#include "../gui/CGuiHandler.h"
+#include "../gui/CursorHandler.h"
+#include "../gui/EventDispatcher.h"
+#include "../gui/MouseButton.h"
+#include "../CMT.h"
+#include "../CPlayerInterface.h"
+#include "../CGameInfo.h"
+
+#include "../../lib/CConfigHandler.h"
+
+#include <SDL_events.h>
+#include <SDL_hints.h>
+
+InputHandler::InputHandler()
+	: mouseHandler(std::make_unique<InputSourceMouse>())
+	, keyboardHandler(std::make_unique<InputSourceKeyboard>())
+	, fingerHandler(std::make_unique<InputSourceTouch>())
+	, textHandler(std::make_unique<InputSourceText>())
+	, userHandler(std::make_unique<UserEventHandler>())
+	, mouseButtonsMask(0)
+	, pointerSpeedMultiplier(settings["general"]["relativePointerSpeedMultiplier"].Float())
+{
+}
+
+InputHandler::~InputHandler() = default;
+
+void InputHandler::handleCurrentEvent(const SDL_Event & current)
+{
+	switch (current.type)
+	{
+		case SDL_KEYDOWN:
+			return keyboardHandler->handleEventKeyDown(current.key);
+		case SDL_KEYUP:
+			return keyboardHandler->handleEventKeyUp(current.key);
+		case SDL_MOUSEMOTION:
+			return mouseHandler->handleEventMouseMotion(current.motion);
+		case SDL_MOUSEBUTTONDOWN:
+			return mouseHandler->handleEventMouseButtonDown(current.button);
+		case SDL_MOUSEWHEEL:
+			return mouseHandler->handleEventMouseWheel(current.wheel);
+		case SDL_TEXTINPUT:
+			return textHandler->handleEventTextInput(current.text);
+		case SDL_TEXTEDITING:
+			return textHandler->handleEventTextEditing(current.edit);
+		case SDL_MOUSEBUTTONUP:
+			return mouseHandler->handleEventMouseButtonUp(current.button);
+		case SDL_FINGERMOTION:
+			return fingerHandler->handleEventFingerMotion(current.tfinger);
+		case SDL_FINGERDOWN:
+			return fingerHandler->handleEventFingerDown(current.tfinger);
+		case SDL_FINGERUP:
+			return fingerHandler->handleEventFingerUp(current.tfinger);
+	}
+}
+
+void InputHandler::processEvents()
+{
+	boost::unique_lock<boost::mutex> lock(eventsMutex);
+	for (auto const & currentEvent : eventsQueue)
+	{
+		if (currentEvent.type == SDL_MOUSEMOTION)
+		{
+			cursorPosition = Point(currentEvent.motion.x, currentEvent.motion.y);
+			mouseButtonsMask = currentEvent.motion.state;
+		}
+		handleCurrentEvent(currentEvent);
+	}
+	eventsQueue.clear();
+}
+
+bool InputHandler::ignoreEventsUntilInput()
+{
+	bool inputFound = false;
+
+	boost::unique_lock<boost::mutex> lock(eventsMutex);
+	for (auto const & event : eventsQueue)
+	{
+		switch(event.type)
+		{
+			case SDL_MOUSEBUTTONDOWN:
+			case SDL_FINGERDOWN:
+			case SDL_KEYDOWN:
+				inputFound = true;
+		}
+	}
+	eventsQueue.clear();
+
+	return inputFound;
+}
+
+void InputHandler::preprocessEvent(const SDL_Event & ev)
+{
+	if((ev.type==SDL_QUIT) ||(ev.type == SDL_KEYDOWN && ev.key.keysym.sym==SDLK_F4 && (ev.key.keysym.mod & KMOD_ALT)))
+	{
+#ifdef VCMI_ANDROID
+		handleQuit(false);
+#else
+		handleQuit();
+#endif
+		return;
+	}
+#ifdef VCMI_ANDROID
+	else if (ev.type == SDL_KEYDOWN && ev.key.keysym.scancode == SDL_SCANCODE_AC_BACK)
+	{
+		handleQuit(true);
+	}
+#endif
+	else if(ev.type == SDL_KEYDOWN && ev.key.keysym.sym==SDLK_F4)
+	{
+		Settings full = settings.write["video"]["fullscreen"];
+		full->Bool() = !full->Bool();
+		return;
+	}
+	else if(ev.type == SDL_USEREVENT)
+	{
+		userHandler->handleUserEvent(ev.user);
+
+		return;
+	}
+	else if(ev.type == SDL_WINDOWEVENT)
+	{
+		switch (ev.window.event) {
+		case SDL_WINDOWEVENT_RESTORED:
+#ifndef VCMI_IOS
+			{
+				boost::unique_lock<boost::recursive_mutex> lock(*CPlayerInterface::pim);
+				GH.onScreenResize();
+			}
+#endif
+			break;
+		}
+		return;
+	}
+	else if(ev.type == SDL_SYSWMEVENT)
+	{
+		if(!settings["session"]["headless"].Bool() && settings["general"]["notifications"].Bool())
+		{
+			NotificationHandler::handleSdlEvent(ev);
+		}
+	}
+
+	//preprocessing
+	if(ev.type == SDL_MOUSEMOTION)
+	{
+		if (CCS && CCS->curh)
+			CCS->curh->cursorMove(ev.motion.x, ev.motion.y);
+	}
+
+	{
+		boost::unique_lock<boost::mutex> lock(eventsMutex);
+
+		if(ev.type == SDL_MOUSEMOTION && !eventsQueue.empty() && eventsQueue.back().type == SDL_MOUSEMOTION)
+		{
+			// In a sequence of mouse motion events, skip all but the last one.
+			// This prevents freezes when every motion event takes longer to handle than interval at which
+			// the events arrive (like dragging on the minimap in world view, with redraw at every event)
+			// so that the events would start piling up faster than they can be processed.
+			eventsQueue.back() = ev;
+			return;
+		}
+		eventsQueue.push_back(ev);
+	}
+}
+
+void InputHandler::fetchEvents()
+{
+	SDL_Event ev;
+
+	while(1 == SDL_PollEvent(&ev))
+	{
+		preprocessEvent(ev);
+	}
+}
+
+bool InputHandler::isKeyboardCtrlDown() const
+{
+#ifdef VCMI_MAC
+	return SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_LGUI] || SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_RGUI];
+#else
+	return SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_LCTRL] || SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_RCTRL];
+#endif
+}
+
+bool InputHandler::isKeyboardAltDown() const
+{
+	return SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_LALT] || SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_RALT];
+}
+
+bool InputHandler::isKeyboardShiftDown() const
+{
+	return SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_LSHIFT] || SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_RSHIFT];
+}
+
+
+void InputHandler::fakeMoveCursor(float dx, float dy)
+{
+	int x, y, w, h;
+
+	SDL_Event event;
+	SDL_MouseMotionEvent sme = {SDL_MOUSEMOTION, 0, 0, 0, 0, 0, 0, 0, 0};
+
+	sme.state = SDL_GetMouseState(&x, &y);
+	SDL_GetWindowSize(mainWindow, &w, &h);
+
+	sme.x = GH.getCursorPosition().x + (int)(pointerSpeedMultiplier * w * dx);
+	sme.y = GH.getCursorPosition().y + (int)(pointerSpeedMultiplier * h * dy);
+
+	vstd::abetween(sme.x, 0, w);
+	vstd::abetween(sme.y, 0, h);
+
+	event.motion = sme;
+	SDL_PushEvent(&event);
+}
+
+void InputHandler::startTextInput(const Rect & where)
+{
+	textHandler->startTextInput(where);
+}
+
+void InputHandler::stopTextInput()
+{
+	textHandler->stopTextInput();
+}
+
+bool InputHandler::isMouseButtonPressed(MouseButton button) const
+{
+	static_assert(static_cast<uint32_t>(MouseButton::LEFT)   == SDL_BUTTON_LEFT,   "mismatch between VCMI and SDL enum!");
+	static_assert(static_cast<uint32_t>(MouseButton::MIDDLE) == SDL_BUTTON_MIDDLE, "mismatch between VCMI and SDL enum!");
+	static_assert(static_cast<uint32_t>(MouseButton::RIGHT)  == SDL_BUTTON_RIGHT,  "mismatch between VCMI and SDL enum!");
+	static_assert(static_cast<uint32_t>(MouseButton::EXTRA1) == SDL_BUTTON_X1,     "mismatch between VCMI and SDL enum!");
+	static_assert(static_cast<uint32_t>(MouseButton::EXTRA2) == SDL_BUTTON_X2,     "mismatch between VCMI and SDL enum!");
+
+	uint32_t index = static_cast<uint32_t>(button);
+	return mouseButtonsMask & SDL_BUTTON(index);
+}
+
+void InputHandler::pushUserEvent(EUserEvent usercode, void * userdata)
+{
+	SDL_Event event;
+	event.type = SDL_USEREVENT;
+	event.user.code = static_cast<int32_t>(usercode);
+	event.user.data1 = userdata;
+	SDL_PushEvent(&event);
+}
+
+const Point & InputHandler::getCursorPosition() const
+{
+	return cursorPosition;
+}

+ 77 - 0
client/eventsSDL/InputHandler.h

@@ -0,0 +1,77 @@
+/*
+* InputHandler.h, 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
+*
+*/
+
+#pragma once
+
+#include "../lib/Rect.h"
+
+enum class EUserEvent;
+enum class MouseButton;
+union SDL_Event;
+
+class InputSourceMouse;
+class InputSourceKeyboard;
+class InputSourceTouch;
+class InputSourceText;
+class UserEventHandler;
+
+class InputHandler
+{
+	std::vector<SDL_Event> eventsQueue;
+	boost::mutex eventsMutex;
+
+	Point cursorPosition;
+	float pointerSpeedMultiplier;
+	int mouseButtonsMask;
+
+	void preprocessEvent(const SDL_Event & event);
+	void handleCurrentEvent(const SDL_Event & current);
+
+	std::unique_ptr<InputSourceMouse> mouseHandler;
+	std::unique_ptr<InputSourceKeyboard> keyboardHandler;
+	std::unique_ptr<InputSourceTouch> fingerHandler;
+	std::unique_ptr<InputSourceText> textHandler;
+	std::unique_ptr<UserEventHandler> userHandler;
+
+public:
+	InputHandler();
+	~InputHandler();
+
+	/// Fetches events from SDL input system and prepares them for processing
+	void fetchEvents();
+	/// Performs actual processing and dispatching of previously fetched events
+	void processEvents();
+
+	/// drops all incoming events without processing them
+	/// returns true if input event has been found
+	bool ignoreEventsUntilInput();
+
+	void fakeMoveCursor(float dx, float dy);
+
+	/// Initiates text input in selected area, potentially creating IME popup (mobile systems only at the moment)
+	void startTextInput(const Rect & where);
+
+	/// Ends any existing text input state
+	void stopTextInput();
+
+	/// Returns true if selected mouse button is pressed at the moment
+	bool isMouseButtonPressed(MouseButton button) const;
+
+	/// Generates new user event that will be processed on next frame
+	void pushUserEvent(EUserEvent usercode, void * userdata);
+
+	/// Returns current position of cursor, in VCMI logical screen coordinates
+	const Point & getCursorPosition() const;
+
+	/// returns true if chosen keyboard key is currently pressed down
+	bool isKeyboardAltDown() const;
+	bool isKeyboardCtrlDown() const;
+	bool isKeyboardShiftDown() const;
+};

+ 85 - 0
client/eventsSDL/InputSourceKeyboard.cpp

@@ -0,0 +1,85 @@
+/*
+* InputSourceKeyboard.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
+*
+*/
+
+#include "StdInc.h"
+#include "InputSourceKeyboard.h"
+
+#include "../../lib/CConfigHandler.h"
+#include "../CPlayerInterface.h"
+#include "../gui/CGuiHandler.h"
+#include "../gui/EventDispatcher.h"
+#include "../gui/ShortcutHandler.h"
+
+#include <SDL_events.h>
+#include <SDL_hints.h>
+
+InputSourceKeyboard::InputSourceKeyboard()
+{
+#ifdef VCMI_MAC
+	// Ctrl+click should be treated as a right click on Mac OS X
+	SDL_SetHint(SDL_HINT_MAC_CTRL_CLICK_EMULATE_RIGHT_CLICK, "1");
+#endif
+}
+
+void InputSourceKeyboard::handleEventKeyDown(const SDL_KeyboardEvent & key)
+{
+	if(key.repeat != 0)
+		return; // ignore periodic event resends
+
+	assert(key.state == SDL_PRESSED);
+
+	if(key.keysym.sym >= SDLK_F1 && key.keysym.sym <= SDLK_F15 && settings["session"]["spectate"].Bool())
+	{
+		//TODO: we need some central place for all interface-independent hotkeys
+		Settings s = settings.write["session"];
+		switch(key.keysym.sym)
+		{
+			case SDLK_F5:
+				if(settings["session"]["spectate-locked-pim"].Bool())
+					CPlayerInterface::pim->unlock();
+				else
+					CPlayerInterface::pim->lock();
+				s["spectate-locked-pim"].Bool() = !settings["session"]["spectate-locked-pim"].Bool();
+				break;
+
+			case SDLK_F6:
+				s["spectate-ignore-hero"].Bool() = !settings["session"]["spectate-ignore-hero"].Bool();
+				break;
+
+			case SDLK_F7:
+				s["spectate-skip-battle"].Bool() = !settings["session"]["spectate-skip-battle"].Bool();
+				break;
+
+			case SDLK_F8:
+				s["spectate-skip-battle-result"].Bool() = !settings["session"]["spectate-skip-battle-result"].Bool();
+				break;
+
+			default:
+				break;
+		}
+		return;
+	}
+
+	auto shortcutsVector = GH.shortcuts().translateKeycode(key.keysym.sym);
+
+	GH.events().dispatchShortcutPressed(shortcutsVector);
+}
+
+void InputSourceKeyboard::handleEventKeyUp(const SDL_KeyboardEvent & key)
+{
+	if(key.repeat != 0)
+		return; // ignore periodic event resends
+
+	assert(key.state == SDL_RELEASED);
+
+	auto shortcutsVector = GH.shortcuts().translateKeycode(key.keysym.sym);
+
+	GH.events().dispatchShortcutReleased(shortcutsVector);
+}

+ 23 - 0
client/eventsSDL/InputSourceKeyboard.h

@@ -0,0 +1,23 @@
+/*
+* InputSourceKeyboard.h, 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
+*
+*/
+
+#pragma once
+
+struct SDL_KeyboardEvent;
+
+/// Class that handles keyboard input from SDL events
+class InputSourceKeyboard
+{
+public:
+	InputSourceKeyboard();
+
+	void handleEventKeyDown(const SDL_KeyboardEvent & current);
+	void handleEventKeyUp(const SDL_KeyboardEvent & current);
+};

+ 72 - 0
client/eventsSDL/InputSourceMouse.cpp

@@ -0,0 +1,72 @@
+/*
+* InputSourceMouse.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
+*
+*/
+
+#include "StdInc.h"
+#include "InputSourceMouse.h"
+
+#include "../../lib/Point.h"
+#include "../gui/CGuiHandler.h"
+#include "../gui/EventDispatcher.h"
+#include "../gui/MouseButton.h"
+
+#include <SDL_events.h>
+
+void InputSourceMouse::handleEventMouseMotion(const SDL_MouseMotionEvent & motion)
+{
+	GH.events().dispatchMouseMoved(Point(motion.x, motion.y));
+}
+
+void InputSourceMouse::handleEventMouseButtonDown(const SDL_MouseButtonEvent & button)
+{
+	Point position(button.x, button.y);
+
+	switch(button.button)
+	{
+		case SDL_BUTTON_LEFT:
+			if(button.clicks > 1)
+				GH.events().dispatchMouseDoubleClick(position);
+			else
+				GH.events().dispatchMouseButtonPressed(MouseButton::LEFT, position);
+			break;
+		case SDL_BUTTON_RIGHT:
+			GH.events().dispatchMouseButtonPressed(MouseButton::RIGHT, position);
+			break;
+		case SDL_BUTTON_MIDDLE:
+			GH.events().dispatchMouseButtonPressed(MouseButton::MIDDLE, position);
+			break;
+	}
+}
+
+void InputSourceMouse::handleEventMouseWheel(const SDL_MouseWheelEvent & wheel)
+{
+	// SDL doesn't have the proper values for mouse positions on SDL_MOUSEWHEEL, refetch them
+	int x = 0, y = 0;
+	SDL_GetMouseState(&x, &y);
+
+	GH.events().dispatchMouseScrolled(Point(wheel.x, wheel.y), Point(x, y));
+}
+
+void InputSourceMouse::handleEventMouseButtonUp(const SDL_MouseButtonEvent & button)
+{
+	Point position(button.x, button.y);
+
+	switch(button.button)
+	{
+		case SDL_BUTTON_LEFT:
+			GH.events().dispatchMouseButtonReleased(MouseButton::LEFT, position);
+			break;
+		case SDL_BUTTON_RIGHT:
+			GH.events().dispatchMouseButtonReleased(MouseButton::RIGHT, position);
+			break;
+		case SDL_BUTTON_MIDDLE:
+			GH.events().dispatchMouseButtonReleased(MouseButton::MIDDLE, position);
+			break;
+	}
+}

+ 25 - 0
client/eventsSDL/InputSourceMouse.h

@@ -0,0 +1,25 @@
+/*
+* InputSourceMouse.h, 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
+*
+*/
+
+#pragma once
+
+struct SDL_MouseWheelEvent;
+struct SDL_MouseMotionEvent;
+struct SDL_MouseButtonEvent;
+
+/// Class that handles mouse input from SDL events
+class InputSourceMouse
+{
+public:
+	void handleEventMouseMotion(const SDL_MouseMotionEvent & current);
+	void handleEventMouseButtonDown(const SDL_MouseButtonEvent & current);
+	void handleEventMouseWheel(const SDL_MouseWheelEvent & current);
+	void handleEventMouseButtonUp(const SDL_MouseButtonEvent & current);
+};

+ 92 - 0
client/eventsSDL/InputSourceText.cpp

@@ -0,0 +1,92 @@
+/*
+* InputSourceText.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
+*
+*/
+
+#include "StdInc.h"
+#include "InputSourceText.h"
+
+#include "../CMT.h"
+#include "../gui/CGuiHandler.h"
+#include "../gui/EventDispatcher.h"
+
+#include "../../lib/Rect.h"
+
+#include <SDL_events.h>
+#include <SDL_render.h>
+
+#ifdef VCMI_APPLE
+#	include <dispatch/dispatch.h>
+#endif
+
+#ifdef VCMI_IOS
+#	include "ios/utils.h"
+#endif
+
+void InputSourceText::handleEventTextInput(const SDL_TextInputEvent & text)
+{
+	GH.events().dispatchTextInput(text.text);
+}
+
+void InputSourceText::handleEventTextEditing(const SDL_TextEditingEvent & text)
+{
+	GH.events().dispatchTextEditing(text.text);
+}
+
+void InputSourceText::startTextInput(const Rect & whereInput)
+{
+#ifdef VCMI_APPLE
+	dispatch_async(dispatch_get_main_queue(), ^{
+#endif
+
+	// TODO ios: looks like SDL bug actually, try fixing there
+	auto renderer = SDL_GetRenderer(mainWindow);
+	float scaleX, scaleY;
+	SDL_Rect viewport;
+	SDL_RenderGetScale(renderer, &scaleX, &scaleY);
+	SDL_RenderGetViewport(renderer, &viewport);
+
+#ifdef VCMI_IOS
+	const auto nativeScale = iOS_utils::screenScale();
+	scaleX /= nativeScale;
+	scaleY /= nativeScale;
+#endif
+
+	SDL_Rect rectInScreenCoordinates;
+	rectInScreenCoordinates.x = (viewport.x + whereInput.x) * scaleX;
+	rectInScreenCoordinates.y = (viewport.y + whereInput.y) * scaleY;
+	rectInScreenCoordinates.w = whereInput.w * scaleX;
+	rectInScreenCoordinates.h = whereInput.h * scaleY;
+
+	SDL_SetTextInputRect(&rectInScreenCoordinates);
+
+	if (SDL_IsTextInputActive() == SDL_FALSE)
+	{
+		SDL_StartTextInput();
+	}
+
+#ifdef VCMI_APPLE
+	});
+#endif
+}
+
+void InputSourceText::stopTextInput()
+{
+#ifdef VCMI_APPLE
+	dispatch_async(dispatch_get_main_queue(), ^{
+#endif
+
+	if (SDL_IsTextInputActive() == SDL_TRUE)
+	{
+		SDL_StopTextInput();
+	}
+
+#ifdef VCMI_APPLE
+	});
+#endif
+}

+ 29 - 0
client/eventsSDL/InputSourceText.h

@@ -0,0 +1,29 @@
+/*
+* InputSourceText.h, 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
+*
+*/
+
+#pragma once
+
+VCMI_LIB_NAMESPACE_BEGIN
+class Rect;
+VCMI_LIB_NAMESPACE_END
+
+struct SDL_TextEditingEvent;
+struct SDL_TextInputEvent;
+
+/// Class that handles text input (e.g. IME or direct input from physical keyboard) from SDL events
+class InputSourceText
+{
+public:
+	void handleEventTextInput(const SDL_TextInputEvent & current);
+	void handleEventTextEditing(const SDL_TextEditingEvent & current);
+
+	void startTextInput(const Rect & where);
+	void stopTextInput();
+};

+ 135 - 0
client/eventsSDL/InputSourceTouch.cpp

@@ -0,0 +1,135 @@
+/*
+* InputSourceTouch.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
+*
+*/
+
+#include "StdInc.h"
+#include "InputSourceTouch.h"
+
+#include "InputHandler.h"
+
+#include "../../lib/CConfigHandler.h"
+#include "../CMT.h"
+#include "../gui/CGuiHandler.h"
+#include "../gui/EventDispatcher.h"
+#include "../gui/MouseButton.h"
+
+#include <SDL_events.h>
+#include <SDL_render.h>
+#include <SDL_hints.h>
+
+InputSourceTouch::InputSourceTouch()
+	: multifinger(false)
+	, isPointerRelativeMode(settings["general"]["userRelativePointer"].Bool())
+{
+	if(isPointerRelativeMode)
+	{
+		SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "0");
+		SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0");
+	}
+}
+
+void InputSourceTouch::handleEventFingerMotion(const SDL_TouchFingerEvent & tfinger)
+{
+	if(isPointerRelativeMode)
+	{
+		GH.input().fakeMoveCursor(tfinger.dx, tfinger.dy);
+	}
+}
+
+void InputSourceTouch::handleEventFingerDown(const SDL_TouchFingerEvent & tfinger)
+{
+	auto fingerCount = SDL_GetNumTouchFingers(tfinger.touchId);
+
+	multifinger = fingerCount > 1;
+
+	if(isPointerRelativeMode)
+	{
+		if(tfinger.x > 0.5)
+		{
+			bool isRightClick = tfinger.y < 0.5;
+
+			fakeMouseButtonEventRelativeMode(true, isRightClick);
+		}
+	}
+#ifndef VCMI_IOS
+	else if(fingerCount == 2)
+	{
+		Point position = convertTouchToMouse(tfinger);
+
+		GH.events().dispatchMouseMoved(position);
+		GH.events().dispatchMouseButtonPressed(MouseButton::RIGHT, position);
+	}
+#endif //VCMI_IOS
+}
+
+void InputSourceTouch::handleEventFingerUp(const SDL_TouchFingerEvent & tfinger)
+{
+#ifndef VCMI_IOS
+	auto fingerCount = SDL_GetNumTouchFingers(tfinger.touchId);
+#endif //VCMI_IOS
+
+	if(isPointerRelativeMode)
+	{
+		if(tfinger.x > 0.5)
+		{
+			bool isRightClick = tfinger.y < 0.5;
+
+			fakeMouseButtonEventRelativeMode(false, isRightClick);
+		}
+	}
+#ifndef VCMI_IOS
+	else if(multifinger)
+	{
+		Point position = convertTouchToMouse(tfinger);
+		GH.events().dispatchMouseMoved(position);
+		GH.events().dispatchMouseButtonReleased(MouseButton::RIGHT, position);
+		multifinger = fingerCount != 0;
+	}
+#endif //VCMI_IOS
+}
+
+Point InputSourceTouch::convertTouchToMouse(const SDL_TouchFingerEvent & tfinger)
+{
+	return Point(tfinger.x * GH.screenDimensions().x, tfinger.y * GH.screenDimensions().y);
+}
+
+void InputSourceTouch::fakeMouseButtonEventRelativeMode(bool down, bool right)
+{
+	SDL_Event event;
+	SDL_MouseButtonEvent sme = {SDL_MOUSEBUTTONDOWN, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+
+	if(!down)
+	{
+		sme.type = SDL_MOUSEBUTTONUP;
+	}
+
+	sme.button = right ? SDL_BUTTON_RIGHT : SDL_BUTTON_LEFT;
+
+	sme.x = GH.getCursorPosition().x;
+	sme.y = GH.getCursorPosition().y;
+
+	float xScale, yScale;
+	int w, h, rLogicalWidth, rLogicalHeight;
+
+	SDL_GetWindowSize(mainWindow, &w, &h);
+	SDL_RenderGetLogicalSize(mainRenderer, &rLogicalWidth, &rLogicalHeight);
+	SDL_RenderGetScale(mainRenderer, &xScale, &yScale);
+
+	SDL_EventState(SDL_MOUSEMOTION, SDL_IGNORE);
+	moveCursorToPosition(Point((int)(sme.x * xScale) + (w - rLogicalWidth * xScale) / 2, (int)(sme.y * yScale + (h - rLogicalHeight * yScale) / 2)));
+	SDL_EventState(SDL_MOUSEMOTION, SDL_ENABLE);
+
+	event.button = sme;
+	SDL_PushEvent(&event);
+}
+
+void InputSourceTouch::moveCursorToPosition(const Point & position)
+{
+	SDL_WarpMouseInWindow(mainWindow, position.x, position.y);
+}

+ 37 - 0
client/eventsSDL/InputSourceTouch.h

@@ -0,0 +1,37 @@
+/*
+* InputSourceTouch.h, 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
+*
+*/
+
+#pragma once
+
+VCMI_LIB_NAMESPACE_BEGIN
+class Point;
+VCMI_LIB_NAMESPACE_END
+
+struct SDL_TouchFingerEvent;
+
+/// Class that handles touchscreen input from SDL events
+class InputSourceTouch
+{
+	bool multifinger;
+	bool isPointerRelativeMode;
+
+	/// moves mouse pointer into specified position inside vcmi window
+	void moveCursorToPosition(const Point & position);
+	Point convertTouchToMouse(const SDL_TouchFingerEvent & current);
+
+	void fakeMouseButtonEventRelativeMode(bool down, bool right);
+
+public:
+	InputSourceTouch();
+
+	void handleEventFingerMotion(const SDL_TouchFingerEvent & current);
+	void handleEventFingerDown(const SDL_TouchFingerEvent & current);
+	void handleEventFingerUp(const SDL_TouchFingerEvent & current);
+};

+ 2 - 2
client/gui/NotificationHandler.cpp → client/eventsSDL/NotificationHandler.cpp

@@ -10,11 +10,11 @@
 
 #include "StdInc.h"
 #include "NotificationHandler.h"
-#include <SDL_video.h>
-#include <SDL_events.h>
 
 #if defined(VCMI_WINDOWS)
 #include <SDL_syswm.h>
+#include <SDL_video.h>
+#include <SDL_events.h>
 
 #define WIN32_LEAN_AND_MEAN		// Exclude rarely-used stuff from Windows headers
 // Windows Header Files:

+ 0 - 0
client/gui/NotificationHandler.h → client/eventsSDL/NotificationHandler.h


+ 87 - 0
client/eventsSDL/UserEventHandler.cpp

@@ -0,0 +1,87 @@
+/*
+* EventHandlerSDLUser.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
+*
+*/
+
+#include "StdInc.h"
+#include "UserEventHandler.h"
+
+#include "../CMT.h"
+#include "../CPlayerInterface.h"
+#include "../CServerHandler.h"
+#include "../gui/CGuiHandler.h"
+#include "../gui/WindowHandler.h"
+#include "../mainmenu/CMainMenu.h"
+#include "../mainmenu/CPrologEpilogVideo.h"
+
+#include <SDL_events.h>
+
+void UserEventHandler::handleUserEvent(const SDL_UserEvent & user)
+{
+	switch(static_cast<EUserEvent>(user.code))
+	{
+		case EUserEvent::FORCE_QUIT:
+		{
+			handleQuit(false);
+			return;
+		}
+		break;
+		case EUserEvent::RETURN_TO_MAIN_MENU:
+		{
+			CSH->endGameplay();
+			GH.defActionsDef = 63;
+			CMM->menu->switchToTab("main");
+		}
+		break;
+		case EUserEvent::RESTART_GAME:
+		{
+			CSH->sendRestartGame();
+		}
+		break;
+		case EUserEvent::CAMPAIGN_START_SCENARIO:
+		{
+			CSH->campaignServerRestartLock.set(true);
+			CSH->endGameplay();
+			auto ourCampaign = std::shared_ptr<CCampaignState>(reinterpret_cast<CCampaignState *>(user.data1));
+			auto & epilogue = ourCampaign->camp->scenarios[ourCampaign->mapsConquered.back()].epilog;
+			auto finisher = [=]()
+			{
+				if(!ourCampaign->mapsRemaining.empty())
+				{
+					GH.windows().pushWindow(CMM);
+					GH.windows().pushWindow(CMM->menu);
+					CMM->openCampaignLobby(ourCampaign);
+				}
+			};
+			if(epilogue.hasPrologEpilog)
+			{
+				GH.windows().createAndPushWindow<CPrologEpilogVideo>(epilogue, finisher);
+			}
+			else
+			{
+				CSH->campaignServerRestartLock.waitUntil(false);
+				finisher();
+			}
+		}
+		break;
+		case EUserEvent::RETURN_TO_MENU_LOAD:
+			CSH->endGameplay();
+			GH.defActionsDef = 63;
+			CMM->menu->switchToTab("load");
+			break;
+		case EUserEvent::FULLSCREEN_TOGGLED:
+		{
+			boost::unique_lock<boost::recursive_mutex> lock(*CPlayerInterface::pim);
+			GH.onScreenResize();
+			break;
+		}
+		default:
+			logGlobal->error("Unknown user event. Code %d", user.code);
+			break;
+	}
+}

+ 20 - 0
client/eventsSDL/UserEventHandler.h

@@ -0,0 +1,20 @@
+/*
+* EventHandlerSDLUser.h, 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
+*
+*/
+
+#pragma once
+
+struct SDL_UserEvent;
+
+/// Class for handling events of type SDL_UserEvent
+class UserEventHandler
+{
+public:
+	void handleUserEvent(const SDL_UserEvent & current);
+};

+ 32 - 540
client/gui/CGuiHandler.cpp

@@ -16,6 +16,8 @@
 #include "ShortcutHandler.h"
 #include "FramerateManager.h"
 #include "WindowHandler.h"
+#include "EventDispatcher.h"
+#include "../eventsSDL/InputHandler.h"
 
 #include "../CGameInfo.h"
 #include "../render/Colors.h"
@@ -29,23 +31,9 @@
 #include "../../lib/CConfigHandler.h"
 
 #include <SDL_render.h>
-#include <SDL_timer.h>
-#include <SDL_events.h>
-#include <SDL_keycode.h>
-
-#ifdef VCMI_APPLE
-#include <dispatch/dispatch.h>
-#endif
-
-#ifdef VCMI_IOS
-#include "ios/utils.h"
-#endif
 
 CGuiHandler GH;
 
-extern std::queue<SDL_Event> SDLEventsQueue;
-extern boost::mutex eventsM;
-
 boost::thread_specific_ptr<bool> inGuiThread;
 
 SObjectConstruction::SObjectConstruction(CIntObject *obj)
@@ -77,515 +65,44 @@ SSetCaptureState::~SSetCaptureState()
 	GH.defActionsDef = prevActions;
 }
 
-static inline void
-processList(const ui16 mask, const ui16 flag, std::list<CIntObject*> *lst, std::function<void (std::list<CIntObject*> *)> cb)
-{
-	if (mask & flag)
-		cb(lst);
-}
-
-void CGuiHandler::processLists(const ui16 activityFlag, std::function<void (std::list<CIntObject*> *)> cb)
-{
-	processList(CIntObject::LCLICK,activityFlag,&lclickable,cb);
-	processList(CIntObject::RCLICK,activityFlag,&rclickable,cb);
-	processList(CIntObject::MCLICK,activityFlag,&mclickable,cb);
-	processList(CIntObject::HOVER,activityFlag,&hoverable,cb);
-	processList(CIntObject::MOVE,activityFlag,&motioninterested,cb);
-	processList(CIntObject::KEYBOARD,activityFlag,&keyinterested,cb);
-	processList(CIntObject::TIME,activityFlag,&timeinterested,cb);
-	processList(CIntObject::WHEEL,activityFlag,&wheelInterested,cb);
-	processList(CIntObject::DOUBLECLICK,activityFlag,&doubleClickInterested,cb);
-	processList(CIntObject::TEXTINPUT,activityFlag,&textInterested,cb);
-}
-
 void CGuiHandler::init()
 {
+	inputHandlerInstance = std::make_unique<InputHandler>();
+	eventDispatcherInstance = std::make_unique<EventDispatcher>();
 	windowHandlerInstance = std::make_unique<WindowHandler>();
 	screenHandlerInstance = std::make_unique<ScreenHandler>();
 	shortcutsHandlerInstance = std::make_unique<ShortcutHandler>();
 	framerateManagerInstance = std::make_unique<FramerateManager>(settings["video"]["targetfps"].Integer());
-
-	isPointerRelativeMode = settings["general"]["userRelativePointer"].Bool();
-	pointerSpeedMultiplier = settings["general"]["relativePointerSpeedMultiplier"].Float();
-}
-
-void CGuiHandler::handleElementActivate(CIntObject * elem, ui16 activityFlag)
-{
-	processLists(activityFlag,[&](std::list<CIntObject*> * lst){
-		lst->push_front(elem);
-	});
-	elem->active_m |= activityFlag;
-}
-
-void CGuiHandler::handleElementDeActivate(CIntObject * elem, ui16 activityFlag)
-{
-	processLists(activityFlag,[&](std::list<CIntObject*> * lst){
-		auto hlp = std::find(lst->begin(),lst->end(),elem);
-		assert(hlp != lst->end());
-		lst->erase(hlp);
-	});
-	elem->active_m &= ~activityFlag;
-}
-
-void CGuiHandler::updateTime()
-{
-	int ms = framerateManager().getElapsedMilliseconds();
-	std::list<CIntObject*> hlp = timeinterested;
-	for (auto & elem : hlp)
-	{
-		if(!vstd::contains(timeinterested,elem)) continue;
-		(elem)->tick(ms);
-	}
 }
 
 void CGuiHandler::handleEvents()
 {
+	events().dispatchTimer(framerate().getElapsedMilliseconds());
+
 	//player interface may want special event handling
 	if(nullptr != LOCPLINT && LOCPLINT->capturedAllEvents())
 		return;
 
-	boost::unique_lock<boost::mutex> lock(eventsM);
-	while(!SDLEventsQueue.empty())
-	{
-		continueEventHandling = true;
-		SDL_Event currentEvent = SDLEventsQueue.front();
-
-		if (currentEvent.type == SDL_MOUSEMOTION)
-		{
-			cursorPosition = Point(currentEvent.motion.x, currentEvent.motion.y);
-			mouseButtonsMask = currentEvent.motion.state;
-		}
-		SDLEventsQueue.pop();
-
-		// In a sequence of mouse motion events, skip all but the last one.
-		// This prevents freezes when every motion event takes longer to handle than interval at which
-		// the events arrive (like dragging on the minimap in world view, with redraw at every event)
-		// so that the events would start piling up faster than they can be processed.
-		if ((currentEvent.type == SDL_MOUSEMOTION) && !SDLEventsQueue.empty() && (SDLEventsQueue.front().type == SDL_MOUSEMOTION))
-			continue;
-
-		handleCurrentEvent(currentEvent);
-	}
-}
-
-void CGuiHandler::convertTouchToMouse(SDL_Event * current)
-{
-	int rLogicalWidth, rLogicalHeight;
-
-	SDL_RenderGetLogicalSize(mainRenderer, &rLogicalWidth, &rLogicalHeight);
-
-	int adjustedMouseY = (int)(current->tfinger.y * rLogicalHeight);
-	int adjustedMouseX = (int)(current->tfinger.x * rLogicalWidth);
-
-	current->button.x = adjustedMouseX;
-	current->motion.x = adjustedMouseX;
-	current->button.y = adjustedMouseY;
-	current->motion.y = adjustedMouseY;
-}
-
-void CGuiHandler::fakeMoveCursor(float dx, float dy)
-{
-	int x, y, w, h;
-
-	SDL_Event event;
-	SDL_MouseMotionEvent sme = {SDL_MOUSEMOTION, 0, 0, 0, 0, 0, 0, 0, 0};
-
-	sme.state = SDL_GetMouseState(&x, &y);
-	SDL_GetWindowSize(mainWindow, &w, &h);
-
-	sme.x = CCS->curh->position().x + (int)(GH.pointerSpeedMultiplier * w * dx);
-	sme.y = CCS->curh->position().y + (int)(GH.pointerSpeedMultiplier * h * dy);
-
-	vstd::abetween(sme.x, 0, w);
-	vstd::abetween(sme.y, 0, h);
-
-	event.motion = sme;
-	SDL_PushEvent(&event);
+	input().processEvents();
 }
 
 void CGuiHandler::fakeMouseMove()
 {
-	fakeMoveCursor(0, 0);
+	input().fakeMoveCursor(0, 0);
 }
 
 void CGuiHandler::startTextInput(const Rect & whereInput)
 {
-#ifdef VCMI_APPLE
-	dispatch_async(dispatch_get_main_queue(), ^{
-#endif
-
-	// TODO ios: looks like SDL bug actually, try fixing there
-	auto renderer = SDL_GetRenderer(mainWindow);
-	float scaleX, scaleY;
-	SDL_Rect viewport;
-	SDL_RenderGetScale(renderer, &scaleX, &scaleY);
-	SDL_RenderGetViewport(renderer, &viewport);
-
-#ifdef VCMI_IOS
-	const auto nativeScale = iOS_utils::screenScale();
-	scaleX /= nativeScale;
-	scaleY /= nativeScale;
-#endif
-
-	SDL_Rect rectInScreenCoordinates;
-	rectInScreenCoordinates.x = (viewport.x + whereInput.x) * scaleX;
-	rectInScreenCoordinates.y = (viewport.y + whereInput.y) * scaleY;
-	rectInScreenCoordinates.w = whereInput.w * scaleX;
-	rectInScreenCoordinates.h = whereInput.h * scaleY;
-
-	SDL_SetTextInputRect(&rectInScreenCoordinates);
-
-	if (SDL_IsTextInputActive() == SDL_FALSE)
-	{
-		SDL_StartTextInput();
-	}
-
-#ifdef VCMI_APPLE
-	});
-#endif
+	input().startTextInput(whereInput);
 }
 
 void CGuiHandler::stopTextInput()
 {
-#ifdef VCMI_APPLE
-	dispatch_async(dispatch_get_main_queue(), ^{
-#endif
-
-	if (SDL_IsTextInputActive() == SDL_TRUE)
-	{
-		SDL_StopTextInput();
-	}
-
-#ifdef VCMI_APPLE
-	});
-#endif
-}
-
-void CGuiHandler::fakeMouseButtonEventRelativeMode(bool down, bool right)
-{
-	SDL_Event event;
-	SDL_MouseButtonEvent sme = {SDL_MOUSEBUTTONDOWN, 0, 0, 0, 0, 0, 0, 0, 0, 0};
-
-	if(!down)
-	{
-		sme.type = SDL_MOUSEBUTTONUP;
-	}
-
-	sme.button = right ? SDL_BUTTON_RIGHT : SDL_BUTTON_LEFT;
-
-	sme.x = CCS->curh->position().x;
-	sme.y = CCS->curh->position().y;
-
-	float xScale, yScale;
-	int w, h, rLogicalWidth, rLogicalHeight;
-
-	SDL_GetWindowSize(mainWindow, &w, &h);
-	SDL_RenderGetLogicalSize(mainRenderer, &rLogicalWidth, &rLogicalHeight);
-	SDL_RenderGetScale(mainRenderer, &xScale, &yScale);
-
-	SDL_EventState(SDL_MOUSEMOTION, SDL_IGNORE);
-	moveCursorToPosition( Point(
-		(int)(sme.x * xScale) + (w - rLogicalWidth * xScale) / 2,
-		(int)(sme.y * yScale + (h - rLogicalHeight * yScale) / 2)));
-	SDL_EventState(SDL_MOUSEMOTION, SDL_ENABLE);
-
-	event.button = sme;
-	SDL_PushEvent(&event);
-}
-
-void CGuiHandler::handleCurrentEvent( SDL_Event & current )
-{
-	if(current.type == SDL_KEYDOWN || current.type == SDL_KEYUP)
-	{
-		SDL_KeyboardEvent key = current.key;
-
-		if (key.repeat != 0)
-			return; // ignore periodic event resends
-
-		if(current.type == SDL_KEYDOWN && key.keysym.sym >= SDLK_F1 && key.keysym.sym <= SDLK_F15 && settings["session"]["spectate"].Bool())
-		{
-			//TODO: we need some central place for all interface-independent hotkeys
-			Settings s = settings.write["session"];
-			switch(key.keysym.sym)
-			{
-			case SDLK_F5:
-				if(settings["session"]["spectate-locked-pim"].Bool())
-					LOCPLINT->pim->unlock();
-				else
-					LOCPLINT->pim->lock();
-				s["spectate-locked-pim"].Bool() = !settings["session"]["spectate-locked-pim"].Bool();
-				break;
-
-			case SDLK_F6:
-				s["spectate-ignore-hero"].Bool() = !settings["session"]["spectate-ignore-hero"].Bool();
-				break;
-
-			case SDLK_F7:
-				s["spectate-skip-battle"].Bool() = !settings["session"]["spectate-skip-battle"].Bool();
-				break;
-
-			case SDLK_F8:
-				s["spectate-skip-battle-result"].Bool() = !settings["session"]["spectate-skip-battle-result"].Bool();
-				break;
-
-			case SDLK_F9:
-				//not working yet since CClient::run remain locked after BattleInterface removal
-//				if(LOCPLINT->battleInt)
-//				{
-//					GH.windows().popInts(1);
-//					vstd::clear_pointer(LOCPLINT->battleInt);
-//				}
-				break;
-
-			default:
-				break;
-			}
-			return;
-		}
-
-		auto shortcutsVector = shortcutsHandler().translateKeycode(key.keysym.sym);
-
-		bool keysCaptured = false;
-		for(auto i = keyinterested.begin(); i != keyinterested.end() && continueEventHandling; i++)
-		{
-			for (EShortcut shortcut : shortcutsVector)
-			{
-				if((*i)->captureThisKey(shortcut))
-				{
-					keysCaptured = true;
-					break;
-				}
-			}
-		}
-
-		std::list<CIntObject*> miCopy = keyinterested;
-		for(auto i = miCopy.begin(); i != miCopy.end() && continueEventHandling; i++)
-		{
-			for (EShortcut shortcut : shortcutsVector)
-			{
-				if(vstd::contains(keyinterested,*i) && (!keysCaptured || (*i)->captureThisKey(shortcut)))
-				{
-					if (key.state == SDL_PRESSED)
-						(**i).keyPressed(shortcut);
-					if (key.state == SDL_RELEASED)
-						(**i).keyReleased(shortcut);
-				}
-			}
-		}
-	}
-	else if(current.type == SDL_MOUSEMOTION)
-	{
-		handleMouseMotion(current);
-	}
-	else if(current.type == SDL_MOUSEBUTTONDOWN)
-	{
-		switch(current.button.button)
-		{
-		case SDL_BUTTON_LEFT:
-		{
-			auto doubleClicked = false;
-			if(lastClick == getCursorPosition() && (SDL_GetTicks() - lastClickTime) < 300)
-			{
-				std::list<CIntObject*> hlp = doubleClickInterested;
-				for(auto i = hlp.begin(); i != hlp.end() && continueEventHandling; i++)
-				{
-					if(!vstd::contains(doubleClickInterested, *i)) continue;
-					if((*i)->pos.isInside(current.motion.x, current.motion.y))
-					{
-						(*i)->onDoubleClick();
-						doubleClicked = true;
-					}
-				}
-
-			}
-
-			lastClick = current.motion;
-			lastClickTime = SDL_GetTicks();
-
-			if(!doubleClicked)
-				handleMouseButtonClick(lclickable, MouseButton::LEFT, true);
-			break;
-		}
-		case SDL_BUTTON_RIGHT:
-			handleMouseButtonClick(rclickable, MouseButton::RIGHT, true);
-			break;
-		case SDL_BUTTON_MIDDLE:
-			handleMouseButtonClick(mclickable, MouseButton::MIDDLE, true);
-			break;
-		default:
-			break;
-		}
-	}
-	else if(current.type == SDL_MOUSEWHEEL)
-	{
-		std::list<CIntObject*> hlp = wheelInterested;
-		for(auto i = hlp.begin(); i != hlp.end() && continueEventHandling; i++)
-		{
-			if(!vstd::contains(wheelInterested,*i)) continue;
-			// SDL doesn't have the proper values for mouse positions on SDL_MOUSEWHEEL, refetch them
-			int x = 0, y = 0;
-			SDL_GetMouseState(&x, &y);
-			(*i)->wheelScrolled(current.wheel.y < 0, (*i)->pos.isInside(x, y));
-		}
-	}
-	else if(current.type == SDL_TEXTINPUT)
-	{
-		for(auto it : textInterested)
-		{
-			it->textInputed(current.text.text);
-		}
-	}
-	else if(current.type == SDL_TEXTEDITING)
-	{
-		for(auto it : textInterested)
-		{
-			it->textEdited(current.edit.text);
-		}
-	}
-	else if(current.type == SDL_MOUSEBUTTONUP)
-	{
-		if(!multifinger)
-		{
-			switch(current.button.button)
-			{
-			case SDL_BUTTON_LEFT:
-				handleMouseButtonClick(lclickable, MouseButton::LEFT, false);
-				break;
-			case SDL_BUTTON_RIGHT:
-				handleMouseButtonClick(rclickable, MouseButton::RIGHT, false);
-				break;
-			case SDL_BUTTON_MIDDLE:
-				handleMouseButtonClick(mclickable, MouseButton::MIDDLE, false);
-				break;
-			}
-		}
-	}
-	else if(current.type == SDL_FINGERMOTION)
-	{
-		if(isPointerRelativeMode)
-		{
-			fakeMoveCursor(current.tfinger.dx, current.tfinger.dy);
-		}
-	}
-	else if(current.type == SDL_FINGERDOWN)
-	{
-		auto fingerCount = SDL_GetNumTouchFingers(current.tfinger.touchId);
-
-		multifinger = fingerCount > 1;
-
-		if(isPointerRelativeMode)
-		{
-			if(current.tfinger.x > 0.5)
-			{
-				bool isRightClick = current.tfinger.y < 0.5;
-
-				fakeMouseButtonEventRelativeMode(true, isRightClick);
-			}
-		}
-#ifndef VCMI_IOS
-		else if(fingerCount == 2)
-		{
-			convertTouchToMouse(&current);
-			handleMouseMotion(current);
-			handleMouseButtonClick(rclickable, MouseButton::RIGHT, true);
-		}
-#endif //VCMI_IOS
-	}
-	else if(current.type == SDL_FINGERUP)
-	{
-#ifndef VCMI_IOS
-		auto fingerCount = SDL_GetNumTouchFingers(current.tfinger.touchId);
-#endif //VCMI_IOS
-
-		if(isPointerRelativeMode)
-		{
-			if(current.tfinger.x > 0.5)
-			{
-				bool isRightClick = current.tfinger.y < 0.5;
-
-				fakeMouseButtonEventRelativeMode(false, isRightClick);
-			}
-		}
-#ifndef VCMI_IOS
-		else if(multifinger)
-		{
-			convertTouchToMouse(&current);
-			handleMouseMotion(current);
-			handleMouseButtonClick(rclickable, MouseButton::RIGHT, false);
-			multifinger = fingerCount != 0;
-		}
-#endif //VCMI_IOS
-	}
-} //event end
-
-void CGuiHandler::handleMouseButtonClick(CIntObjectList & interestedObjs, MouseButton btn, bool isPressed)
-{
-	auto hlp = interestedObjs;
-	for(auto i = hlp.begin(); i != hlp.end() && continueEventHandling; i++)
-	{
-		if(!vstd::contains(interestedObjs, *i)) continue;
-
-		auto prev = (*i)->mouseState(btn);
-		if(!isPressed)
-			(*i)->updateMouseState(btn, isPressed);
-		if((*i)->pos.isInside(getCursorPosition()))
-		{
-			if(isPressed)
-				(*i)->updateMouseState(btn, isPressed);
-			(*i)->click(btn, isPressed, prev);
-		}
-		else if(!isPressed)
-			(*i)->click(btn, boost::logic::indeterminate, prev);
-	}
-}
-
-void CGuiHandler::handleMouseMotion(const SDL_Event & current)
-{
-	//sending active, hovered hoverable objects hover() call
-	std::vector<CIntObject*> hlp;
-
-	auto hoverableCopy = hoverable;
-	for(auto & elem : hoverableCopy)
-	{
-		if(elem->pos.isInside(getCursorPosition()))
-		{
-			if (!(elem)->hovered)
-				hlp.push_back((elem));
-		}
-		else if ((elem)->hovered)
-		{
-			(elem)->hover(false);
-			(elem)->hovered = false;
-		}
-	}
-
-	for(auto & elem : hlp)
-	{
-		elem->hover(true);
-		elem->hovered = true;
-	}
-
-	// do not send motion events for events outside our window
-	//if (current.motion.windowID == 0)
-		handleMoveInterested(current.motion);
-}
-
-void CGuiHandler::handleMoveInterested(const SDL_MouseMotionEvent & motion)
-{
-	//sending active, MotionInterested objects mouseMoved() call
-	std::list<CIntObject*> miCopy = motioninterested;
-	for(auto & elem : miCopy)
-	{
-		if(elem->strongInterest || Rect::createAround(elem->pos, 1).isInside( motion.x, motion.y)) //checking bounds including border fixes bug #2476
-		{
-			(elem)->mouseMoved(Point(motion.x, motion.y));
-		}
-	}
+	input().stopTextInput();
 }
 
 void CGuiHandler::renderFrame()
 {
-
 	// Updating GUI requires locking pim mutex (that protects screen and GUI state).
 	// During game:
 	// When ending the game, the pim mutex might be hold by other thread,
@@ -619,17 +136,12 @@ void CGuiHandler::renderFrame()
 		windows().onFrameRendered();
 	}
 
-	framerateManager().framerateDelay(); // holds a constant FPS
+	framerate().framerateDelay(); // holds a constant FPS
 }
 
 CGuiHandler::CGuiHandler()
-	: lastClick(-500, -500)
-	, lastClickTime(0)
-	, defActionsDef(0)
+	: defActionsDef(0)
 	, captureChildren(false)
-	, multifinger(false)
-	, mouseButtonsMask(0)
-	, continueEventHandling(true)
 	, curInt(nullptr)
 	, fakeStatusBar(std::make_shared<EmptyStatusBar>())
 	, terminate_cond (new CondSh<bool>(false))
@@ -641,50 +153,36 @@ CGuiHandler::~CGuiHandler()
 	delete terminate_cond;
 }
 
-ShortcutHandler & CGuiHandler::shortcutsHandler()
+ShortcutHandler & CGuiHandler::shortcuts()
 {
 	assert(shortcutsHandlerInstance);
 	return *shortcutsHandlerInstance;
 }
 
-FramerateManager & CGuiHandler::framerateManager()
+FramerateManager & CGuiHandler::framerate()
 {
 	assert(framerateManagerInstance);
 	return *framerateManagerInstance;
 }
 
-void CGuiHandler::moveCursorToPosition(const Point & position)
-{
-	SDL_WarpMouseInWindow(mainWindow, position.x, position.y);
-}
-
 bool CGuiHandler::isKeyboardCtrlDown() const
 {
-#ifdef VCMI_MAC
-	return SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_LGUI] || SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_RGUI];
-#else
-	return SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_LCTRL] || SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_RCTRL];
-#endif
+	return inputHandlerInstance->isKeyboardCtrlDown();
 }
 
 bool CGuiHandler::isKeyboardAltDown() const
 {
-	return SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_LALT] || SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_RALT];
+	return inputHandlerInstance->isKeyboardAltDown();
 }
 
 bool CGuiHandler::isKeyboardShiftDown() const
 {
-	return SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_LSHIFT] || SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_RSHIFT];
-}
-
-void CGuiHandler::breakEventHandling()
-{
-	continueEventHandling = false;
+	return inputHandlerInstance->isKeyboardShiftDown();
 }
 
 const Point & CGuiHandler::getCursorPosition() const
 {
-	return cursorPosition;
+	return inputHandlerInstance->getCursorPosition();
 }
 
 Point CGuiHandler::screenDimensions() const
@@ -692,21 +190,9 @@ Point CGuiHandler::screenDimensions() const
 	return Point(screen->w, screen->h);
 }
 
-bool CGuiHandler::isMouseButtonPressed() const
-{
-	return mouseButtonsMask > 0;
-}
-
 bool CGuiHandler::isMouseButtonPressed(MouseButton button) const
 {
-	static_assert(static_cast<uint32_t>(MouseButton::LEFT)   == SDL_BUTTON_LEFT,   "mismatch between VCMI and SDL enum!");
-	static_assert(static_cast<uint32_t>(MouseButton::MIDDLE) == SDL_BUTTON_MIDDLE, "mismatch between VCMI and SDL enum!");
-	static_assert(static_cast<uint32_t>(MouseButton::RIGHT)  == SDL_BUTTON_RIGHT,  "mismatch between VCMI and SDL enum!");
-	static_assert(static_cast<uint32_t>(MouseButton::EXTRA1) == SDL_BUTTON_X1,     "mismatch between VCMI and SDL enum!");
-	static_assert(static_cast<uint32_t>(MouseButton::EXTRA2) == SDL_BUTTON_X2,     "mismatch between VCMI and SDL enum!");
-
-	uint32_t index = static_cast<uint32_t>(button);
-	return mouseButtonsMask & SDL_BUTTON(index);
+	return inputHandlerInstance->isMouseButtonPressed(button);
 }
 
 void CGuiHandler::drawFPSCounter()
@@ -714,7 +200,7 @@ void CGuiHandler::drawFPSCounter()
 	static SDL_Rect overlay = { 0, 0, 64, 32};
 	uint32_t black = SDL_MapRGB(screen->format, 10, 10, 10);
 	SDL_FillRect(screen, &overlay, black);
-	std::string fps = std::to_string(framerateManager().getFramerate());
+	std::string fps = std::to_string(framerate().getFramerate());
 	graphics->fonts[FONT_BIG]->renderTextLeft(screen, fps, Colors::YELLOW, Point(10, 10));
 }
 
@@ -725,16 +211,12 @@ bool CGuiHandler::amIGuiThread()
 
 void CGuiHandler::pushUserEvent(EUserEvent usercode)
 {
-	pushUserEvent(usercode, nullptr);
+	inputHandlerInstance->pushUserEvent(usercode, nullptr);
 }
 
 void CGuiHandler::pushUserEvent(EUserEvent usercode, void * userdata)
 {
-	SDL_Event event;
-	event.type = SDL_USEREVENT;
-	event.user.code = static_cast<int32_t>(usercode);
-	event.user.data1 = userdata;
-	SDL_PushEvent(&event);
+	inputHandlerInstance->pushUserEvent(usercode, userdata);
 }
 
 IScreenHandler & CGuiHandler::screenHandler()
@@ -742,6 +224,16 @@ IScreenHandler & CGuiHandler::screenHandler()
 	return *screenHandlerInstance;
 }
 
+EventDispatcher & CGuiHandler::events()
+{
+	return *eventDispatcherInstance;
+}
+
+InputHandler & CGuiHandler::input()
+{
+	return *inputHandlerInstance;
+}
+
 WindowHandler & CGuiHandler::windows()
 {
 	assert(windowHandlerInstance);

+ 17 - 68
client/gui/CGuiHandler.h

@@ -9,48 +9,38 @@
  */
 #pragma once
 
-#include "MouseButton.h"
-#include "../../lib/Point.h"
-
 VCMI_LIB_NAMESPACE_BEGIN
-
 template <typename T> struct CondSh;
+class Point;
 class Rect;
-
 VCMI_LIB_NAMESPACE_END
 
-union SDL_Event;
-struct SDL_MouseMotionEvent;
-
+enum class MouseButton;
 class ShortcutHandler;
 class FramerateManager;
 class IStatusBar;
 class CIntObject;
 class IUpdateable;
 class IShowActivatable;
-class IShowable;
 class IScreenHandler;
 class WindowHandler;
+class EventDispatcher;
+class InputHandler;
 
-// TODO: event handling need refactoring
+// TODO: event handling need refactoring. Perhaps convert into delayed function call?
 enum class EUserEvent
 {
-	/*CHANGE_SCREEN_RESOLUTION = 1,*/
-	RETURN_TO_MAIN_MENU = 2,
-	//STOP_CLIENT = 3,
-	RESTART_GAME = 4,
+	RETURN_TO_MAIN_MENU,
+	RESTART_GAME,
 	RETURN_TO_MENU_LOAD,
 	FULLSCREEN_TOGGLED,
 	CAMPAIGN_START_SCENARIO,
-	FORCE_QUIT, //quit client without question
+	FORCE_QUIT,
 };
 
 // Handles GUI logic and drawing
 class CGuiHandler
 {
-public:
-
-
 private:
 	/// Fake no-op version status bar, for use in windows that have no status bar
 	std::shared_ptr<IStatusBar> fakeStatusBar;
@@ -58,57 +48,27 @@ private:
 	/// Status bar of current window, if any. Uses weak_ptr to allow potential hanging reference after owned window has been deleted
 	std::weak_ptr<IStatusBar> currentStatusBar;
 
-	Point cursorPosition;
-	uint32_t mouseButtonsMask;
-
 	std::unique_ptr<ShortcutHandler> shortcutsHandlerInstance;
 	std::unique_ptr<WindowHandler> windowHandlerInstance;
 
-	std::atomic<bool> continueEventHandling;
-	using CIntObjectList = std::list<CIntObject *>;
-
-	//active GUI elements (listening for events
-	CIntObjectList lclickable;
-	CIntObjectList rclickable;
-	CIntObjectList mclickable;
-	CIntObjectList hoverable;
-	CIntObjectList keyinterested;
-	CIntObjectList motioninterested;
-	CIntObjectList timeinterested;
-	CIntObjectList wheelInterested;
-	CIntObjectList doubleClickInterested;
-	CIntObjectList textInterested;
-
 	std::unique_ptr<IScreenHandler> screenHandlerInstance;
 	std::unique_ptr<FramerateManager> framerateManagerInstance;
-
-	void handleMouseButtonClick(CIntObjectList & interestedObjs, MouseButton btn, bool isPressed);
-	void processLists(const ui16 activityFlag, std::function<void (std::list<CIntObject*> *)> cb);
-	void handleCurrentEvent(SDL_Event &current);
-	void handleMouseMotion(const SDL_Event & current);
-	void handleMoveInterested( const SDL_MouseMotionEvent & motion );
-	void convertTouchToMouse(SDL_Event * current);
-	void fakeMoveCursor(float dx, float dy);
-	void fakeMouseButtonEventRelativeMode(bool down, bool right);
+	std::unique_ptr<EventDispatcher> eventDispatcherInstance;
+	std::unique_ptr<InputHandler> inputHandlerInstance;
 
 public:
-	void handleElementActivate(CIntObject * elem, ui16 activityFlag);
-	void handleElementDeActivate(CIntObject * elem, ui16 activityFlag);
-public:
-
 	/// returns current position of mouse cursor, relative to vcmi window
 	const Point & getCursorPosition() const;
 
-	ShortcutHandler & shortcutsHandler();
-	FramerateManager & framerateManager();
+	ShortcutHandler & shortcuts();
+	FramerateManager & framerate();
+	EventDispatcher & events();
+	InputHandler & input();
 
 	/// Returns current logical screen dimensions
 	/// May not match size of window if user has UI scaling different from 100%
 	Point screenDimensions() const;
 
-	/// returns true if at least one mouse button is pressed
-	bool isMouseButtonPressed() const;
-
 	/// returns true if specified mouse button is pressed
 	bool isMouseButtonPressed(MouseButton button) const;
 
@@ -120,9 +80,6 @@ public:
 	void startTextInput(const Rect & where);
 	void stopTextInput();
 
-	/// moves mouse pointer into specified position inside vcmi window
-	void moveCursorToPosition(const Point & position);
-
 	IScreenHandler & screenHandler();
 
 	WindowHandler & windows();
@@ -135,12 +92,6 @@ public:
 
 	IUpdateable *curInt;
 
-	Point lastClick;
-	unsigned lastClickTime;
-	bool multifinger;
-	bool isPointerRelativeMode;
-	float pointerSpeedMultiplier;
-
 	ui8 defActionsDef; //default auto actions
 	bool captureChildren; //all newly created objects will get their parents from stack and will be added to parents children list
 	std::list<CIntObject *> createdObj; //stack of objs being created
@@ -154,15 +105,13 @@ public:
 	/// called whenever user selects different resolution, requiring to center/resize all windows
 	void onScreenResize();
 
-	void updateTime(); //handles timeInterested
 	void handleEvents(); //takes events from queue and calls interested objects
 	void fakeMouseMove();
-	void breakEventHandling(); //current event won't be propagated anymore
 	void drawFPSCounter(); // draws the FPS to the upper left corner of the screen
 
-	static bool amIGuiThread();
-	static void pushUserEvent(EUserEvent usercode);
-	static void pushUserEvent(EUserEvent usercode, void * userdata);
+	bool amIGuiThread();
+	void pushUserEvent(EUserEvent usercode);
+	void pushUserEvent(EUserEvent usercode, void * userdata);
 
 	CondSh<bool> * terminate_cond; // confirm termination
 };

+ 34 - 90
client/gui/CIntObject.cpp

@@ -17,20 +17,12 @@
 #include "../windows/CMessage.h"
 #include "../CMT.h"
 
-#include <SDL_pixels.h>
-
-IShowActivatable::IShowActivatable()
-{
-	type = 0;
-}
-
 CIntObject::CIntObject(int used_, Point pos_):
 	parent_m(nullptr),
-	active_m(0),
 	parent(parent_m),
-	active(active_m)
+	type(0)
 {
-	hovered = captureAllKeys = strongInterest = false;
+	captureAllKeys = false;
 	used = used_;
 
 	recActions = defActions = GH.defActionsDef;
@@ -46,7 +38,7 @@ CIntObject::CIntObject(int used_, Point pos_):
 
 CIntObject::~CIntObject()
 {
-	if(active_m)
+	if(isActive())
 		deactivate();
 
 	while(!children.empty())
@@ -76,25 +68,16 @@ void CIntObject::showAll(SDL_Surface * to)
 		for(auto & elem : children)
 			if(elem->recActions & SHOWALL)
 				elem->showAll(to);
-
 	}
 }
 
 void CIntObject::activate()
 {
-	if (active_m)
-	{
-		if ((used | GENERAL) == active_m)
-			return;
-		else
-		{
-			logGlobal->warn("Warning: IntObject re-activated with mismatching used and active");
-			deactivate(); //FIXME: better to avoid such possibility at all
-		}
-	}
+	if (isActive())
+		return;
 
-	active_m |= GENERAL;
-	activate(used);
+	activateEvents(used | GENERAL);
+	assert(isActive());
 
 	if(defActions & ACTIVATE)
 		for(auto & elem : children)
@@ -102,20 +85,14 @@ void CIntObject::activate()
 				elem->activate();
 }
 
-void CIntObject::activate(ui16 what)
-{
-	GH.handleElementActivate(this, what);
-}
-
 void CIntObject::deactivate()
 {
-	if (!active_m)
+	if (!isActive())
 		return;
 
-	active_m &= ~ GENERAL;
-	deactivate(active_m);
+	deactivateEvents(ALL);
 
-	assert(!active_m);
+	assert(!isActive());
 
 	if(defActions & DEACTIVATE)
 		for(auto & elem : children)
@@ -123,65 +100,33 @@ void CIntObject::deactivate()
 				elem->deactivate();
 }
 
-void CIntObject::deactivate(ui16 what)
-{
-	GH.handleElementDeActivate(this, what);
-}
-
-void CIntObject::click(MouseButton btn, tribool down, bool previousState)
-{
-	switch(btn)
-	{
-	default:
-	case MouseButton::LEFT:
-		clickLeft(down, previousState);
-		break;
-	case MouseButton::MIDDLE:
-		clickMiddle(down, previousState);
-		break;
-	case MouseButton::RIGHT:
-		clickRight(down, previousState);
-		break;
-	}
-}
-
-void CIntObject::printAtLoc(const std::string & text, int x, int y, EFonts font, SDL_Color kolor, SDL_Surface * dst)
-{
-	graphics->fonts[font]->renderTextLeft(dst, text, kolor, Point(pos.x + x, pos.y + y));
-}
-
-void CIntObject::printAtMiddleLoc(const std::string & text, int x, int y, EFonts font, SDL_Color kolor, SDL_Surface * dst)
-{
-	printAtMiddleLoc(text, Point(x,y), font, kolor, dst);
-}
-
-void CIntObject::printAtMiddleLoc(const std::string & text, const Point &p, EFonts font, SDL_Color kolor, SDL_Surface * dst)
+void CIntObject::printAtMiddleLoc(const std::string & text, const Point &p, EFonts font, const SDL_Color & kolor, SDL_Surface * dst)
 {
 	graphics->fonts[font]->renderTextCenter(dst, text, kolor, pos.topLeft() + p);
 }
 
-void CIntObject::printAtMiddleWBLoc( const std::string & text, int x, int y, EFonts font, int charpr, SDL_Color kolor, SDL_Surface * dst)
+void CIntObject::printAtMiddleWBLoc( const std::string & text, const Point &p, EFonts font, int charpr, const SDL_Color & kolor, SDL_Surface * dst)
 {
-	graphics->fonts[font]->renderTextLinesCenter(dst, CMessage::breakText(text, charpr, font), kolor, Point(pos.x + x, pos.y + y));
+	graphics->fonts[font]->renderTextLinesCenter(dst, CMessage::breakText(text, charpr, font), kolor, pos.topLeft() + p);
 }
 
 void CIntObject::addUsedEvents(ui16 newActions)
 {
-	if (active_m)
-		activate(~used & newActions);
+	if (isActive())
+		activateEvents(~used & newActions);
 	used |= newActions;
 }
 
 void CIntObject::removeUsedEvents(ui16 newActions)
 {
-	if (active_m)
-		deactivate(used & newActions);
+	if (isActive())
+		deactivateEvents(used & newActions);
 	used &= ~newActions;
 }
 
 void CIntObject::disable()
 {
-	if(active)
+	if(isActive())
 		deactivate();
 
 	recActions = DISPOSE;
@@ -189,7 +134,7 @@ void CIntObject::disable()
 
 void CIntObject::enable()
 {
-	if(!active_m && (!parent_m || parent_m->active))
+	if(!isActive() && (!parent_m || parent_m->isActive()))
 	{
 		activate();
 		redraw();
@@ -246,9 +191,9 @@ void CIntObject::addChild(CIntObject * child, bool adjustPosition)
 	if(adjustPosition)
 		child->moveBy(pos.topLeft(), adjustPosition);
 
-	if (!active && child->active)
+	if (!isActive() && child->isActive())
 		child->deactivate();
-	if (active && !child->active)
+	if (isActive()&& !child->isActive())
 		child->activate();
 }
 
@@ -273,7 +218,7 @@ void CIntObject::redraw()
 {
 	//currently most of calls come from active objects so this check won't affect them
 	//it should fix glitches when called by inactive elements located below active window
-	if (active)
+	if (isActive())
 	{
 		if (parent_m && (type & REDRAW_PARENT))
 		{
@@ -288,6 +233,11 @@ void CIntObject::redraw()
 	}
 }
 
+bool CIntObject::isInside(const Point & position)
+{
+	return pos.isInside(position);
+}
+
 void CIntObject::onScreenResize()
 {
 	center(pos, true);
@@ -320,6 +270,7 @@ bool CIntObject::captureThisKey(EShortcut key)
 
 CKeyShortcut::CKeyShortcut()
 	: assignedKey(EShortcut::NONE)
+	, shortcutPressed(false)
 {}
 
 CKeyShortcut::CKeyShortcut(EShortcut key)
@@ -329,23 +280,19 @@ CKeyShortcut::CKeyShortcut(EShortcut key)
 
 void CKeyShortcut::keyPressed(EShortcut key)
 {
-	if( assignedKey == key && assignedKey != EShortcut::NONE)
+	if( assignedKey == key && assignedKey != EShortcut::NONE && !shortcutPressed)
 	{
-		bool prev = mouseState(MouseButton::LEFT);
-		updateMouseState(MouseButton::LEFT, true);
-		clickLeft(true, prev);
-
+		shortcutPressed = true;
+		clickLeft(true, false);
 	}
 }
 
 void CKeyShortcut::keyReleased(EShortcut key)
 {
-	if( assignedKey == key && assignedKey != EShortcut::NONE)
+	if( assignedKey == key && assignedKey != EShortcut::NONE && shortcutPressed)
 	{
-		bool prev = mouseState(MouseButton::LEFT);
-		updateMouseState(MouseButton::LEFT, false);
-		clickLeft(false, prev);
-
+		shortcutPressed = false;
+		clickLeft(false, true);
 	}
 }
 
@@ -361,6 +308,3 @@ void WindowBase::close()
 		logGlobal->error("Only top interface must be closed");
 	GH.windows().popWindows(1);
 }
-
-IStatusBar::~IStatusBar()
-{}

+ 26 - 96
client/gui/CIntObject.h

@@ -9,83 +9,49 @@
  */
 #pragma once
 
-#include "MouseButton.h"
 #include "../render/Graphics.h"
 #include "../../lib/Rect.h"
+#include "EventsReceiver.h"
 
 struct SDL_Surface;
 class CGuiHandler;
 class CPicture;
-enum class EShortcut;
-
-using boost::logic::tribool;
-
-// Defines a activate/deactive method
-class IActivatable
-{
-public:
-	virtual void activate()=0;
-	virtual void deactivate()=0;
-	virtual ~IActivatable(){};
-};
 
 class IUpdateable
 {
 public:
 	virtual void update()=0;
-	virtual ~IUpdateable(){};
+	virtual ~IUpdateable() = default;
 };
 
-// Defines a show method
-class IShowable
+class IShowActivatable
 {
 public:
+	virtual void activate()=0;
+	virtual void deactivate()=0;
+
 	virtual void redraw()=0;
 	virtual void show(SDL_Surface * to) = 0;
-	virtual void showAll(SDL_Surface * to)
-	{
-		show(to);
-	}
-	virtual ~IShowable(){};
-};
+	virtual void showAll(SDL_Surface * to) = 0;
 
-class IShowActivatable : public IShowable, public IActivatable
-{
-public:
-	//redraw parent flag - this int may be semi-transparent and require redraw of parent window
-	enum {REDRAW_PARENT=8};
-	int type; //bin flags using etype
-	IShowActivatable();
-	virtual ~IShowActivatable(){};
+	virtual void onScreenResize() = 0;
+	virtual ~IShowActivatable() = default;
 };
 
 // Base UI element
-class CIntObject : public IShowActivatable //interface object
+class CIntObject : public IShowActivatable, public AEventsReceiver //interface object
 {
-	ui16 used;//change via addUsed() or delUsed
-
-	std::map<MouseButton, bool> currentMouseState;
+	ui16 used;
 
 	//non-const versions of fields to allow changing them in CIntObject
 	CIntObject *parent_m; //parent object
-	ui16 active_m;
-
-protected:
-	//activate or deactivate specific action (LCLICK, RCLICK...)
-	void activate(ui16 what);
-	void deactivate(ui16 what);
 
 public:
-/*
- * Functions and fields that supposed to be private but are not for now.
- * Don't use them unless you really know what they are for
- */
-	std::vector<CIntObject *> children;
-
+	//redraw parent flag - this int may be semi-transparent and require redraw of parent window
+	enum {REDRAW_PARENT=8};
+	int type; //bin flags using etype
 
-/*
- * Public interface
- */
+	std::vector<CIntObject *> children;
 
 	/// read-only parent access. May not be a "clean" solution but allows some compatibility
 	CIntObject * const & parent;
@@ -96,43 +62,14 @@ public:
 	CIntObject(int used=0, Point offset=Point());
 	virtual ~CIntObject();
 
-	void updateMouseState(MouseButton btn, bool state) { currentMouseState[btn] = state; }
-	bool mouseState(MouseButton btn) const { return currentMouseState.count(btn) ? currentMouseState.at(btn) : false; }
-
-	virtual void click(MouseButton btn, tribool down, bool previousState);
-	virtual void clickLeft(tribool down, bool previousState) {}
-	virtual void clickRight(tribool down, bool previousState) {}
-	virtual void clickMiddle(tribool down, bool previousState) {}
-
 	//hover handling
-	/*const*/ bool hovered;  //for determining if object is hovered
-	virtual void hover (bool on){}
+	void hover (bool on) override{}
 
 	//keyboard handling
 	bool captureAllKeys; //if true, only this object should get info about pressed keys
-	virtual void keyPressed(EShortcut key){}
-	virtual void keyReleased(EShortcut key){}
-	virtual bool captureThisKey(EShortcut key); //allows refining captureAllKeys against specific events (eg. don't capture ENTER)
 
-	virtual void textInputed(const std::string & enteredText){};
-	virtual void textEdited(const std::string & enteredText){};
+	bool captureThisKey(EShortcut key) override; //allows refining captureAllKeys against specific events (eg. don't capture ENTER)
 
-	//mouse movement handling
-	bool strongInterest; //if true - report all mouse movements, if not - only when hovered
-	virtual void mouseMoved (const Point & cursorPosition){}
-
-	//time handling
-	virtual void tick(uint32_t msPassed){}
-
-	//mouse wheel
-	virtual void wheelScrolled(bool down, bool in){}
-
-	//double click
-	virtual void onDoubleClick(){}
-
-	// These are the arguments that can be used to determine what kind of input the CIntObject will receive
-	enum {LCLICK=1, RCLICK=2, HOVER=4, MOVE=8, KEYBOARD=16, TIME=32, GENERAL=64, WHEEL=128, DOUBLECLICK=256, TEXTINPUT=512, MCLICK=1024, ALL=0xffff};
-	const ui16 & active;
 	void addUsedEvents(ui16 newActions);
 	void removeUsedEvents(ui16 newActions);
 
@@ -147,7 +84,6 @@ public:
 	/// deactivates or activates UI element based on flag
 	void setEnabled(bool on);
 
-
 	// activate or deactivate object. Inactive object won't receive any input events (keyboard\mouse)
 	// usually used automatically by parent
 	void activate() override;
@@ -162,7 +98,9 @@ public:
 
 	/// called only for windows whenever screen size changes
 	/// default behavior is to re-center, can be overriden
-	virtual void onScreenResize();
+	void onScreenResize() override;
+
+	bool isInside(const Point & position) override;
 
 	const Rect & center(const Rect &r, bool propagate = true); //sets pos so that r will be in the center of screen, assigns sizes of r to pos, returns new position
 	const Rect & center(const Point &p, bool propagate = true);  //moves object so that point p will be in its center
@@ -173,26 +111,18 @@ public:
 
 	void addChild(CIntObject *child, bool adjustPosition = false);
 	void removeChild(CIntObject *child, bool adjustPosition = false);
-	//delChild - not needed, use normal "delete child" instead
-	//delChildNull - not needed, use "vstd::clear_pointer(child)" instead
-
-/*
- * Functions that should be used only by specific GUI elements. Don't use them unless you really know why they are here
- */
-
-	//functions for printing text. Use CLabel where possible instead
-	void printAtLoc(const std::string & text, int x, int y, EFonts font, SDL_Color color, SDL_Surface * dst);
-	void printAtMiddleLoc(const std::string & text, int x, int y, EFonts font, SDL_Color color, SDL_Surface * dst);
-	void printAtMiddleLoc(const std::string & text, const Point &p, EFonts font, SDL_Color color, SDL_Surface * dst);
-	void printAtMiddleWBLoc(const std::string & text, int x, int y, EFonts font, int charsPerLine, SDL_Color color, SDL_Surface * dst);
 
-	friend class CGuiHandler;
+	/// functions for printing text.
+	/// Deprecated. Use CLabel where possible instead
+	void printAtMiddleLoc(const std::string & text, const Point &p, EFonts font, const SDL_Color & color, SDL_Surface * dst);
+	void printAtMiddleWBLoc(const std::string & text, const Point &p, EFonts font, int charsPerLine, const SDL_Color & color, SDL_Surface * dst);
 };
 
 /// Class for binding keys to left mouse button clicks
 /// Classes wanting use it should have it as one of their base classes
 class CKeyShortcut : public virtual CIntObject
 {
+	bool shortcutPressed;
 public:
 	EShortcut assignedKey;
 	CKeyShortcut();
@@ -213,7 +143,7 @@ protected:
 class IStatusBar
 {
 public:
-	virtual ~IStatusBar();
+	virtual ~IStatusBar() = default;
 
 	/// set current text for the status bar
 	virtual void write(const std::string & text) = 0;

+ 1 - 1
client/gui/CursorHandler.cpp

@@ -251,7 +251,7 @@ void CursorHandler::updateSpellcastCursor()
 {
 	static const float frameDisplayDuration = 0.1f; // H3 uses 100 ms per frame
 
-	frameTime += GH.framerateManager().getElapsedMilliseconds() / 1000.f;
+	frameTime += GH.framerate().getElapsedMilliseconds() / 1000.f;
 	size_t newFrame = frame;
 
 	while (frameTime >= frameDisplayDuration)

+ 244 - 0
client/gui/EventDispatcher.cpp

@@ -0,0 +1,244 @@
+/*
+ * EventDispatcher.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
+ *
+ */
+#include "StdInc.h"
+#include "EventDispatcher.h"
+
+#include "EventsReceiver.h"
+#include "FramerateManager.h"
+#include "CGuiHandler.h"
+#include "MouseButton.h"
+
+#include "../../lib/Point.h"
+
+template<typename Functor>
+void EventDispatcher::processLists(ui16 activityFlag, const Functor & cb)
+{
+	auto processList = [&](ui16 mask, EventReceiversList & lst)
+	{
+		if(mask & activityFlag)
+			cb(lst);
+	};
+
+	processList(AEventsReceiver::LCLICK, lclickable);
+	processList(AEventsReceiver::RCLICK, rclickable);
+	processList(AEventsReceiver::MCLICK, mclickable);
+	processList(AEventsReceiver::HOVER, hoverable);
+	processList(AEventsReceiver::MOVE, motioninterested);
+	processList(AEventsReceiver::KEYBOARD, keyinterested);
+	processList(AEventsReceiver::TIME, timeinterested);
+	processList(AEventsReceiver::WHEEL, wheelInterested);
+	processList(AEventsReceiver::DOUBLECLICK, doubleClickInterested);
+	processList(AEventsReceiver::TEXTINPUT, textInterested);
+}
+
+void EventDispatcher::activateElement(AEventsReceiver * elem, ui16 activityFlag)
+{
+	processLists(activityFlag,[&](EventReceiversList & lst){
+		lst.push_front(elem);
+	});
+	elem->activeState |= activityFlag;
+}
+
+void EventDispatcher::deactivateElement(AEventsReceiver * elem, ui16 activityFlag)
+{
+	processLists(activityFlag,[&](EventReceiversList & lst){
+		auto hlp = std::find(lst.begin(),lst.end(),elem);
+		assert(hlp != lst.end());
+		lst.erase(hlp);
+	});
+	elem->activeState &= ~activityFlag;
+}
+
+void EventDispatcher::dispatchTimer(uint32_t msPassed)
+{
+	EventReceiversList hlp = timeinterested;
+	for (auto & elem : hlp)
+	{
+		if(!vstd::contains(timeinterested,elem)) continue;
+		(elem)->tick(msPassed);
+	}
+}
+
+void EventDispatcher::dispatchShortcutPressed(const std::vector<EShortcut> & shortcutsVector)
+{
+	bool keysCaptured = false;
+
+	for(auto & i : keyinterested)
+		for(EShortcut shortcut : shortcutsVector)
+			if(i->captureThisKey(shortcut))
+				keysCaptured = true;
+
+	EventReceiversList miCopy = keyinterested;
+
+	for(auto & i : miCopy)
+	{
+		for(EShortcut shortcut : shortcutsVector)
+			if(vstd::contains(keyinterested, i) && (!keysCaptured || i->captureThisKey(shortcut)))
+			{
+				i->keyPressed(shortcut);
+				if (keysCaptured)
+					return;
+			}
+	}
+}
+
+void EventDispatcher::dispatchShortcutReleased(const std::vector<EShortcut> & shortcutsVector)
+{
+	bool keysCaptured = false;
+
+	for(auto & i : keyinterested)
+		for(EShortcut shortcut : shortcutsVector)
+			if(i->captureThisKey(shortcut))
+				keysCaptured = true;
+
+	EventReceiversList miCopy = keyinterested;
+
+	for(auto & i : miCopy)
+	{
+		for(EShortcut shortcut : shortcutsVector)
+			if(vstd::contains(keyinterested, i) && (!keysCaptured || i->captureThisKey(shortcut)))
+			{
+				i->keyReleased(shortcut);
+				if (keysCaptured)
+					return;
+			}
+	}
+}
+
+EventDispatcher::EventReceiversList & EventDispatcher::getListForMouseButton(MouseButton button)
+{
+	switch (button)
+	{
+		case MouseButton::LEFT:
+			return lclickable;
+		case MouseButton::RIGHT:
+			return rclickable;
+		case MouseButton::MIDDLE:
+			return mclickable;
+	}
+	throw std::runtime_error("Invalid mouse button in getListForMouseButton");
+}
+
+void EventDispatcher::dispatchMouseDoubleClick(const Point & position)
+{
+	bool doubleClicked = false;
+	auto hlp = doubleClickInterested;
+
+	for(auto & i : hlp)
+	{
+		if(!vstd::contains(doubleClickInterested, i))
+			continue;
+
+		if(i->isInside(position))
+		{
+			i->onDoubleClick();
+			doubleClicked = true;
+		}
+	}
+
+	if(!doubleClicked)
+		dispatchMouseButtonPressed(MouseButton::LEFT, position);
+}
+
+void EventDispatcher::dispatchMouseButtonPressed(const MouseButton & button, const Point & position)
+{
+	handleMouseButtonClick(getListForMouseButton(button), button, true);
+}
+
+void EventDispatcher::dispatchMouseButtonReleased(const MouseButton & button, const Point & position)
+{
+	handleMouseButtonClick(getListForMouseButton(button), button, false);
+}
+
+void EventDispatcher::handleMouseButtonClick(EventReceiversList & interestedObjs, MouseButton btn, bool isPressed)
+{
+	auto hlp = interestedObjs;
+	for(auto & i : hlp)
+	{
+		if(!vstd::contains(interestedObjs, i))
+			continue;
+
+		auto prev = i->isMouseButtonPressed(btn);
+		if(!isPressed)
+			i->currentMouseState[btn] = isPressed;
+		if(i->isInside(GH.getCursorPosition()))
+		{
+			if(isPressed)
+				i->currentMouseState[btn] = isPressed;
+			i->click(btn, isPressed, prev);
+		}
+		else if(!isPressed)
+			i->click(btn, boost::logic::indeterminate, prev);
+	}
+}
+
+void EventDispatcher::dispatchMouseScrolled(const Point & distance, const Point & position)
+{
+	EventReceiversList hlp = wheelInterested;
+	for(auto & i : hlp)
+	{
+		if(!vstd::contains(wheelInterested,i))
+			continue;
+		i->wheelScrolled(distance.y < 0, i->isInside(position));
+	}
+}
+
+void EventDispatcher::dispatchTextInput(const std::string & text)
+{
+	for(auto it : textInterested)
+	{
+		it->textInputed(text);
+	}
+}
+
+void EventDispatcher::dispatchTextEditing(const std::string & text)
+{
+	for(auto it : textInterested)
+	{
+		it->textEdited(text);
+	}
+}
+
+void EventDispatcher::dispatchMouseMoved(const Point & position)
+{
+	//sending active, hovered hoverable objects hover() call
+	EventReceiversList hlp;
+
+	auto hoverableCopy = hoverable;
+	for(auto & elem : hoverableCopy)
+	{
+		if(elem->isInside(GH.getCursorPosition()))
+		{
+			if (!(elem)->isHovered())
+				hlp.push_back((elem));
+		}
+		else if ((elem)->isHovered())
+		{
+			(elem)->hover(false);
+			(elem)->hoveredState = false;
+		}
+	}
+
+	for(auto & elem : hlp)
+	{
+		elem->hover(true);
+		elem->hoveredState = true;
+	}
+
+	//sending active, MotionInterested objects mouseMoved() call
+	EventReceiversList miCopy = motioninterested;
+	for(auto & elem : miCopy)
+	{
+		if(elem->strongInterestState || elem->isInside(position)) //checking bounds including border fixes bug #2476
+		{
+			(elem)->mouseMoved(position);
+		}
+	}
+}

+ 68 - 0
client/gui/EventDispatcher.h

@@ -0,0 +1,68 @@
+/*
+ * EventDispatcher.h, 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
+ *
+ */
+#pragma once
+
+VCMI_LIB_NAMESPACE_BEGIN
+class Point;
+VCMI_LIB_NAMESPACE_END
+
+class AEventsReceiver;
+enum class MouseButton;
+enum class EShortcut;
+
+/// Class that receives events from event producers and dispatches it to UI elements that are interested in this event
+class EventDispatcher
+{
+	using EventReceiversList = std::list<AEventsReceiver *>;
+
+	/// list of UI elements that are interested in particular event
+	EventReceiversList lclickable;
+	EventReceiversList rclickable;
+	EventReceiversList mclickable;
+	EventReceiversList hoverable;
+	EventReceiversList keyinterested;
+	EventReceiversList motioninterested;
+	EventReceiversList timeinterested;
+	EventReceiversList wheelInterested;
+	EventReceiversList doubleClickInterested;
+	EventReceiversList textInterested;
+
+	EventReceiversList & getListForMouseButton(MouseButton button);
+
+	void handleMouseButtonClick(EventReceiversList & interestedObjs, MouseButton btn, bool isPressed);
+
+	template<typename Functor>
+	void processLists(ui16 activityFlag, const Functor & cb);
+
+public:
+	/// add specified UI element as interested. Uses unnamed enum from AEventsReceiver for activity flags
+	void activateElement(AEventsReceiver * elem, ui16 activityFlag);
+
+	/// removes specified UI element as interested for specified activities
+	void deactivateElement(AEventsReceiver * elem, ui16 activityFlag);
+
+	/// Regular timer event
+	void dispatchTimer(uint32_t msPassed);
+
+	/// Shortcut events (e.g. keyboard keys)
+	void dispatchShortcutPressed(const std::vector<EShortcut> & shortcuts);
+	void dispatchShortcutReleased(const std::vector<EShortcut> & shortcuts);
+
+	/// Mouse events
+	void dispatchMouseButtonPressed(const MouseButton & button, const Point & position);
+	void dispatchMouseButtonReleased(const MouseButton & button, const Point & position);
+	void dispatchMouseScrolled(const Point & distance, const Point & position);
+	void dispatchMouseDoubleClick(const Point & position);
+	void dispatchMouseMoved(const Point & position);
+
+	/// Text input events
+	void dispatchTextInput(const std::string & text);
+	void dispatchTextEditing(const std::string & text);
+};

+ 74 - 0
client/gui/EventsReceiver.cpp

@@ -0,0 +1,74 @@
+/*
+ * EventsReceiver.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
+ *
+ */
+#include "StdInc.h"
+#include "EventsReceiver.h"
+
+#include "MouseButton.h"
+#include "CGuiHandler.h"
+#include "EventDispatcher.h"
+
+AEventsReceiver::AEventsReceiver()
+	: activeState(0)
+	, hoveredState(false)
+	, strongInterestState(false)
+{
+}
+
+bool AEventsReceiver::isHovered() const
+{
+	return hoveredState;
+}
+
+bool AEventsReceiver::isActive() const
+{
+	return activeState;
+}
+
+bool AEventsReceiver::isMouseButtonPressed(MouseButton btn) const
+{
+	return currentMouseState.count(btn) ? currentMouseState.at(btn) : false;
+}
+
+void AEventsReceiver::setMoveEventStrongInterest(bool on)
+{
+	strongInterestState = on;
+}
+
+void AEventsReceiver::activateEvents(ui16 what)
+{
+	assert((what & GENERAL) || (activeState & GENERAL));
+
+	activeState |= GENERAL;
+	GH.events().activateElement(this, what);
+}
+
+void AEventsReceiver::deactivateEvents(ui16 what)
+{
+	if (what & GENERAL)
+		activeState &= ~GENERAL;
+	GH.events().deactivateElement(this, what & activeState);
+}
+
+void AEventsReceiver::click(MouseButton btn, tribool down, bool previousState)
+{
+	switch(btn)
+	{
+	default:
+	case MouseButton::LEFT:
+		clickLeft(down, previousState);
+		break;
+	case MouseButton::MIDDLE:
+		clickMiddle(down, previousState);
+		break;
+	case MouseButton::RIGHT:
+		clickRight(down, previousState);
+		break;
+	}
+}

+ 77 - 0
client/gui/EventsReceiver.h

@@ -0,0 +1,77 @@
+/*
+ * EventsReceiver.h, 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
+ *
+ */
+#pragma once
+
+VCMI_LIB_NAMESPACE_BEGIN
+class Point;
+VCMI_LIB_NAMESPACE_END
+
+class EventDispatcher;
+enum class MouseButton;
+enum class EShortcut;
+using boost::logic::tribool;
+
+/// Class that is capable of subscribing and receiving input events
+/// Acts as base class for all UI elements
+class AEventsReceiver
+{
+	friend class EventDispatcher;
+
+	ui16 activeState;
+	bool hoveredState;
+	bool strongInterestState;
+	std::map<MouseButton, bool> currentMouseState;
+
+	void click(MouseButton btn, tribool down, bool previousState);
+protected:
+
+	/// If set, UI element will receive all mouse movement events, even those outside this element
+	void setMoveEventStrongInterest(bool on);
+
+	/// Activates particular events for this UI element. Uses unnamed enum from this class
+	void activateEvents(ui16 what);
+	/// Deactivates particular events for this UI element. Uses unnamed enum from this class
+	void deactivateEvents(ui16 what);
+
+	virtual void clickLeft(tribool down, bool previousState) {}
+	virtual void clickRight(tribool down, bool previousState) {}
+	virtual void clickMiddle(tribool down, bool previousState) {}
+
+	virtual void textInputed(const std::string & enteredText) {}
+	virtual void textEdited(const std::string & enteredText) {}
+
+	virtual void tick(uint32_t msPassed) {}
+	virtual void wheelScrolled(bool down, bool in) {}
+	virtual void mouseMoved(const Point & cursorPosition) {}
+	virtual void hover(bool on) {}
+	virtual void onDoubleClick() {}
+
+	virtual void keyPressed(EShortcut key) {}
+	virtual void keyReleased(EShortcut key) {}
+
+	virtual bool captureThisKey(EShortcut key) = 0;
+	virtual bool isInside(const Point & position) = 0;
+
+public:
+	AEventsReceiver();
+	virtual ~AEventsReceiver() = default;
+
+	/// These are the arguments that can be used to determine what kind of input UI element will receive
+	enum {LCLICK=1, RCLICK=2, HOVER=4, MOVE=8, KEYBOARD=16, TIME=32, GENERAL=64, WHEEL=128, DOUBLECLICK=256, TEXTINPUT=512, MCLICK=1024, ALL=0xffff};
+
+	/// Returns true if element is currently hovered by mouse
+	bool isHovered() const;
+
+	/// Returns true if element is currently active and may receive events
+	bool isActive() const;
+
+	/// Returns true if particular mouse button was pressed when inside this element
+	bool isMouseButtonPressed(MouseButton btn) const;
+};

+ 1 - 1
client/gui/InterfaceObjectConfigurable.cpp

@@ -258,7 +258,7 @@ EShortcut InterfaceObjectConfigurable::readHotkey(const JsonNode & config) const
 		return EShortcut::NONE;
 	}
 
-	EShortcut result = GH.shortcutsHandler().findShortcut(config.String());
+	EShortcut result = GH.shortcuts().findShortcut(config.String());
 	if (result == EShortcut::NONE)
 		logGlobal->error("Invalid hotkey '%s' in interface configuration!", config.String());
 	return result;;

+ 1 - 5
client/gui/WindowHandler.cpp

@@ -109,12 +109,8 @@ void WindowHandler::simpleRedraw()
 void WindowHandler::onScreenResize()
 {
 	for(const auto & entry : windowsStack)
-	{
-		auto intObject = std::dynamic_pointer_cast<CIntObject>(entry);
+		entry->onScreenResize();
 
-		if(intObject)
-			intObject->onScreenResize();
-	}
 	totalRedraw();
 }
 

+ 1 - 1
client/lobby/CSelectionBase.cpp

@@ -90,7 +90,7 @@ CSelectionBase::CSelectionBase(ESelectionScreen type)
 
 void CSelectionBase::toggleTab(std::shared_ptr<CIntObject> tab)
 {
-	if(curTab && curTab->active)
+	if(curTab && curTab->isActive())
 	{
 		curTab->deactivate();
 		curTab->recActions = 0;

+ 4 - 13
client/lobby/RandomMapTab.cpp

@@ -15,6 +15,7 @@
 #include "../CGameInfo.h"
 #include "../CServerHandler.h"
 #include "../gui/CGuiHandler.h"
+#include "../gui/MouseButton.h"
 #include "../gui/WindowHandler.h"
 #include "../widgets/CComponent.h"
 #include "../widgets/Buttons.h"
@@ -397,21 +398,16 @@ void TemplatesDropBox::ListItem::hover(bool on)
 	if(h && w)
 	{
 		if(w->getText().empty())
-		{
-			hovered = false;
 			h->visible = false;
-		}
 		else
-		{
 			h->visible = on;
-		}
 	}
 	redraw();
 }
 
 void TemplatesDropBox::ListItem::clickLeft(tribool down, bool previousState)
 {
-	if(down && hovered)
+	if(down && isHovered())
 	{
 		dropBox.setTemplate(item);
 	}
@@ -469,19 +465,14 @@ void TemplatesDropBox::sliderMove(int slidPos)
 	redraw();
 }
 
-void TemplatesDropBox::hover(bool on)
-{
-	hovered = on;
-}
-
 void TemplatesDropBox::clickLeft(tribool down, bool previousState)
 {
-	if(down && !hovered)
+	if(down && !isActive())
 	{
 		auto w = widget<CSlider>("slider");
 
 		// pop the interface only if the mouse is not clicking on the slider
-		if (!w || !w->mouseState(MouseButton::LEFT))
+		if (!w || !w->isMouseButtonPressed(MouseButton::LEFT))
 		{
 			assert(GH.windows().isTopWindow(this));
 			GH.windows().popWindows(1);

+ 0 - 1
client/lobby/RandomMapTab.h

@@ -70,7 +70,6 @@ class TemplatesDropBox : public InterfaceObjectConfigurable
 public:
 	TemplatesDropBox(RandomMapTab & randomMapTab, int3 size);
 	
-	void hover(bool on) override;
 	void clickLeft(tribool down, bool previousState) override;
 	void setTemplate(const CRmgTemplate *);
 	

+ 2 - 2
client/lobby/SelectionTab.cpp

@@ -277,7 +277,7 @@ void SelectionTab::clickLeft(tribool down, bool previousState)
 		}
 #ifdef VCMI_IOS
 		// focus input field if clicked inside it
-		else if(inputName && inputName->active && inputNameRect.isInside(GH.getCursorPosition()))
+		else if(inputName && inputName->isActive() && inputNameRect.isInside(GH.getCursorPosition()))
 			inputName->giveFocus();
 #endif
 	}
@@ -408,7 +408,7 @@ void SelectionTab::select(int position)
 
 	rememberCurrentSelection();
 
-	if(inputName && inputName->active)
+	if(inputName && inputName->isActive())
 	{
 		auto filename = *CResourceHandler::get("local")->getResourceName(ResourceID(curItems[py]->fileURI, EResType::CLIENT_SAVEGAME));
 		inputName->setText(filename.stem().string());

+ 1 - 1
client/mainmenu/CCampaignScreen.cpp

@@ -119,7 +119,7 @@ void CCampaignScreen::CCampaignButton::show(SDL_Surface * to)
 	CIntObject::show(to);
 
 	// Play the campaign button video when the mouse cursor is placed over the button
-	if(hovered)
+	if(isHovered())
 	{
 		if(CCS->videoh->fname != video)
 			CCS->videoh->open(video);

+ 1 - 2
client/mainmenu/CMainMenu.cpp

@@ -233,7 +233,7 @@ std::shared_ptr<CButton> CMenuEntry::createButton(CMenuScreen * parent, const Js
 	if(posy < 0)
 		posy = pos.h + posy;
 
-	EShortcut shortcut = GH.shortcutsHandler().findShortcut(button["shortcut"].String());
+	EShortcut shortcut = GH.shortcuts().findShortcut(button["shortcut"].String());
 
 	auto result = std::make_shared<CButton>(Point(posx, posy), button["name"].String(), help, command, shortcut);
 
@@ -333,7 +333,6 @@ void CMainMenu::update()
 	}
 
 	// Handles mouse and key input
-	GH.updateTime();
 	GH.handleEvents();
 
 	// check for null othervice crash on finishing a campaign

+ 3 - 2
client/mapView/MapViewActions.cpp

@@ -18,6 +18,7 @@
 #include "../adventureMap/AdventureMapInterface.h"
 #include "../gui/CGuiHandler.h"
 #include "../gui/CursorHandler.h"
+#include "../gui/MouseButton.h"
 
 #include "../../lib/CConfigHandler.h"
 
@@ -105,8 +106,8 @@ void MapViewActions::handleSwipeMove(const Point & cursorPosition)
 	if(!swipeEnabled() && !GH.isMouseButtonPressed(MouseButton::MIDDLE))
 		return;
 
-	// on mobile platforms with enabled swipe any button is enough
-	if(swipeEnabled() && (!GH.isMouseButtonPressed() || GH.multifinger))
+	// on mobile platforms with enabled swipe we use left button
+	if(swipeEnabled() && !GH.isMouseButtonPressed(MouseButton::LEFT))
 		return;
 
 	if(!isSwiping)

+ 1 - 1
client/renderSDL/ScreenHandler.cpp

@@ -13,7 +13,7 @@
 
 #include "../../lib/CConfigHandler.h"
 #include "../gui/CGuiHandler.h"
-#include "../gui/NotificationHandler.h"
+#include "../eventsSDL/NotificationHandler.h"
 #include "../gui/WindowHandler.h"
 #include "CMT.h"
 #include "SDL_Extensions.h"

+ 26 - 21
client/widgets/Buttons.cpp

@@ -19,6 +19,7 @@
 #include "../battle/BattleInterface.h"
 #include "../battle/BattleInterfaceClasses.h"
 #include "../gui/CGuiHandler.h"
+#include "../gui/MouseButton.h"
 #include "../gui/Shortcut.h"
 #include "../windows/InfoWindows.h"
 #include "../render/CAnimation.h"
@@ -54,7 +55,7 @@ void CButton::update()
 		newPos = (int)image->size()-1;
 	image->setFrame(newPos);
 
-	if (active)
+	if (isActive())
 		redraw();
 }
 
@@ -173,22 +174,28 @@ void CButton::clickLeft(tribool down, bool previousState)
 
 	if (down)
 	{
-		if (!soundDisabled)
-			CCS->soundh->playSound(soundBase::button);
-		setState(PRESSED);
-	}
-	else if(hoverable && hovered)
-		setState(HIGHLIGHTED);
-	else
-		setState(NORMAL);
+		if (getState() != PRESSED)
+		{
+			if (!soundDisabled)
+				CCS->soundh->playSound(soundBase::button);
+			setState(PRESSED);
 
-	if (actOnDown && down)
-	{
-		onButtonClicked();
+			if (actOnDown)
+				onButtonClicked();
+		}
 	}
-	else if (!actOnDown && previousState && (down==false))
+	else
 	{
-		onButtonClicked();
+		if (getState() == PRESSED)
+		{
+			if(hoverable && isHovered())
+				setState(HIGHLIGHTED);
+			else
+				setState(NORMAL);
+
+			if (!actOnDown && previousState && (down == false))
+				onButtonClicked();
+		}
 	}
 }
 
@@ -492,7 +499,7 @@ void CVolumeSlider::moveTo(int id)
 	vstd::abetween<int>(id, 0, animImage->size() - 1);
 	animImage->setFrame(id);
 	animImage->moveTo(Point(pos.x + (animImage->pos.w + 1) * id, pos.y));
-	if (active)
+	if (isActive())
 		redraw();
 }
 
@@ -550,8 +557,7 @@ void CVolumeSlider::wheelScrolled(bool down, bool in)
 
 void CSlider::sliderClicked()
 {
-	if(!(active & MOVE))
-		addUsedEvents(MOVE);
+	addUsedEvents(MOVE);
 }
 
 void CSlider::mouseMoved (const Point & cursorPosition)
@@ -688,12 +694,11 @@ void CSlider::clickLeft(tribool down, bool previousState)
 			return;
 		// 		if (rw>1) return;
 		// 		if (rw<0) return;
-		slider->clickLeft(true, slider->mouseState(MouseButton::LEFT));
+		slider->clickLeft(true, slider->isMouseButtonPressed(MouseButton::LEFT));
 		moveTo((int)(rw * positions  +  0.5));
 		return;
 	}
-	if(active & MOVE)
-		removeUsedEvents(MOVE);
+	removeUsedEvents(MOVE);
 }
 
 CSlider::CSlider(Point position, int totalw, std::function<void(int)> Moved, int Capacity, int Amount, int Value, bool Horizontal, CSlider::EStyle style)
@@ -710,7 +715,7 @@ CSlider::CSlider(Point position, int totalw, std::function<void(int)> Moved, int
 	vstd::amax(value, 0);
 	vstd::amin(value, positions);
 
-	strongInterest = true;
+	setMoveEventStrongInterest(true);
 
 	pos.x += position.x;
 	pos.y += position.y;

+ 1 - 1
client/widgets/CArtifactHolder.cpp

@@ -213,7 +213,7 @@ void CHeroArtPlace::showAll(SDL_Surface* to)
 		CIntObject::showAll(to);
 	}
 
-	if(marked && active)
+	if(marked && isActive())
 	{
 		// Draw vertical bars.
 		for(int i = 0; i < pos.h; ++i)

+ 1 - 1
client/widgets/CArtifactsOfHeroBase.cpp

@@ -169,7 +169,7 @@ void CArtifactsOfHeroBase::scrollBackpackForArtSet(int offset, const CArtifactSe
 
 void CArtifactsOfHeroBase::safeRedraw()
 {
-	if(active)
+	if(isActive())
 	{
 		if(parent)
 			parent->redraw();

+ 1 - 1
client/widgets/CWindowWithArtifacts.cpp

@@ -238,7 +238,7 @@ void CWindowWithArtifacts::artifactMoved(const ArtifactLocation & srcLoc, const
 		if(artSetPtr)
 		{
 			const auto hero = artSetPtr->getHero();
-			if(artSetPtr->active)
+			if(artSetPtr->isActive())
 			{
 				if(pickedArtInst)
 				{

+ 3 - 3
client/widgets/ObjectLists.cpp

@@ -35,7 +35,7 @@ std::shared_ptr<CIntObject> CObjectList::createItem(size_t index)
 
 	item->recActions = defActions;
 	addChild(item.get());
-	if (active)
+	if (isActive())
 		item->activate();
 	return item;
 }
@@ -70,7 +70,7 @@ void CTabbedInt::reset()
 	activeTab = createItem(activeID);
 	activeTab->moveTo(pos.topLeft());
 
-	if(active)
+	if(isActive())
 		redraw();
 }
 
@@ -107,7 +107,7 @@ void CListBox::updatePositions()
 		(elem)->moveTo(itemPos);
 		itemPos += itemOffset;
 	}
-	if (active)
+	if (isActive())
 	{
 		redraw();
 		if (slider)

+ 5 - 3
client/widgets/TextControls.cpp

@@ -446,7 +446,7 @@ void CGStatusBar::clickLeft(tribool down, bool previousState)
 {
 	if(!down)
 	{
-		if(LOCPLINT && LOCPLINT->cingconsole->active)
+		if(LOCPLINT && LOCPLINT->cingconsole->isActive())
 			LOCPLINT->cingconsole->startEnteringText();
 	}
 }
@@ -574,7 +574,6 @@ void CTextInput::keyPressed(EShortcut key)
 	if(key == EShortcut::GLOBAL_MOVE_FOCUS)
 	{
 		moveFocus();
-		GH.breakEventHandling();
 		return;
 	}
 
@@ -622,6 +621,9 @@ bool CTextInput::captureThisKey(EShortcut key)
 	if(key == EShortcut::GLOBAL_RETURN)
 		return false;
 
+	if (!focus)
+		return false;
+
 	return true;
 }
 
@@ -749,7 +751,7 @@ void CFocusable::moveFocus()
 		if(i == focusables.end())
 			i = focusables.begin();
 
-		if((*i)->active)
+		if((*i)->isActive())
 		{
 			(*i)->giveFocus();
 			break;

+ 3 - 6
client/windows/CCastleInterface.cpp

@@ -115,13 +115,11 @@ void CBuildingRect::hover(bool on)
 {
 	if(on)
 	{
-		if(!(active & MOVE))
-			addUsedEvents(MOVE);
+		addUsedEvents(MOVE);
 	}
 	else
 	{
-		if(active & MOVE)
-			removeUsedEvents(MOVE);
+		removeUsedEvents(MOVE);
 
 		if(parent->selectedBuilding == this)
 		{
@@ -218,7 +216,7 @@ void CBuildingRect::showAll(SDL_Surface * to)
 		return;
 
 	CShowableAnim::showAll(to);
-	if(!active && parent->selectedBuilding == this && border)
+	if(!isActive() && parent->selectedBuilding == this && border)
 		border->draw(to, pos.x, pos.y);
 }
 
@@ -1577,7 +1575,6 @@ void LabeledValue::hover(bool on)
 	else
 	{
 		GH.statusbar()->clear();
-		parent->hovered = false;
 	}
 }
 

+ 1 - 1
client/windows/CKingdomInterface.cpp

@@ -972,6 +972,6 @@ std::shared_ptr<CIntObject> CHeroItem::onTabSelected(size_t index)
 void CHeroItem::onArtChange(int tabIndex)
 {
 	//redraw item after background change
-	if(active)
+	if(isActive())
 		redraw();
 }

+ 0 - 2
client/windows/CSpellWindow.cpp

@@ -295,7 +295,6 @@ void CSpellWindow::fLcornerb()
 		setCurrentPage(currentPage - 1);
 	}
 	computeSpellsPerArea();
-	GH.breakEventHandling();
 }
 
 void CSpellWindow::fRcornerb()
@@ -306,7 +305,6 @@ void CSpellWindow::fRcornerb()
 		setCurrentPage(currentPage + 1);
 	}
 	computeSpellsPerArea();
-	GH.breakEventHandling();
 }
 
 void CSpellWindow::show(SDL_Surface * to)

+ 1 - 1
client/windows/CTradeWindow.cpp

@@ -1479,7 +1479,7 @@ void CAltarWindow::showAll(SDL_Surface * to)
 			int dmp, val;
 			market->getOffer(pickedArt->getTypeId(), 0, dmp, val, EMarketMode::ARTIFACT_EXP);
 			val = static_cast<int>(hero->calculateXp(val));
-			printAtMiddleLoc(std::to_string(val), 304, 498, FONT_SMALL, Colors::WHITE, to);
+			printAtMiddleLoc(std::to_string(val), Point(304, 498), FONT_SMALL, Colors::WHITE, to);
 		}
 	}
 }

+ 1 - 1
client/windows/GUIClasses.cpp

@@ -534,7 +534,7 @@ void CTavernWindow::show(SDL_Surface * to)
 			recruit->addHoverText(CButton::NORMAL, boost::str(boost::format(CGI->generaltexth->tavernInfo[3]) % sel->h->getNameTranslated() % sel->h->type->heroClass->getNameTranslated()));
 		}
 
-		printAtMiddleWBLoc(sel->description, 146, 395, FONT_SMALL, 200, Colors::WHITE, to);
+		printAtMiddleWBLoc(sel->description, Point(146, 395), FONT_SMALL, 200, Colors::WHITE, to);
 		CSDL_Ext::drawBorder(to,sel->pos.x-2,sel->pos.y-2,sel->pos.w+4,sel->pos.h+4,Colors::BRIGHT_YELLOW);
 	}
 }