Jelajahi Sumber

Moved input handling from GuiHandler to set of classes in eventsSDL dir

Ivan Savenko 2 tahun lalu
induk
melakukan
5e86b00dda

+ 8 - 147
client/CMT.cpp

@@ -14,36 +14,27 @@
 
 #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
 
 #ifdef VCMI_ANDROID
 #include "../lib/CAndroidVMHelper.h"
@@ -54,15 +45,14 @@
 #undef main
 #endif
 
+extern boost::mutex eventsM;
+
 namespace po = boost::program_options;
 namespace po_style = boost::program_options::command_line_style;
 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
@@ -331,7 +321,7 @@ int main(int argc, char * argv[])
 #endif
 
 #ifdef SDL_HINT_MOUSE_TOUCH_EVENTS
-	if(GH.isPointerRelativeMode)
+	if(settings["general"]["userRelativePointer"].Bool())
 	{
 		SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "0");
 		SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0");
@@ -469,147 +459,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();
 	}

+ 17 - 3
client/CMakeLists.txt

@@ -27,6 +27,14 @@ 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
@@ -34,7 +42,6 @@ set(client_SRCS
 	gui/EventsReceiver.cpp
 	gui/InterfaceObjectConfigurable.cpp
 	gui/FramerateManager.cpp
-	gui/NotificationHandler.cpp
 	gui/ShortcutHandler.cpp
 	gui/WindowHandler.cpp
 
@@ -162,6 +169,14 @@ 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
@@ -170,7 +185,6 @@ set(client_HEADERS
 	gui/InterfaceObjectConfigurable.h
 	gui/FramerateManager.h
 	gui/MouseButton.h
-	gui/NotificationHandler.h
 	gui/Shortcut.h
 	gui/ShortcutHandler.h
 	gui/TextAlignment.h
@@ -337,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>"

+ 1 - 1
client/CPlayerInterface.cpp

@@ -74,7 +74,7 @@
 #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>

+ 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
 			{

+ 1 - 1
client/CVideoHandler.cpp

@@ -371,7 +371,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 )
 	{

+ 250 - 0
client/eventsSDL/InputHandler.cpp

@@ -0,0 +1,250 @@
+/*
+* 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 "../CMT.h"
+#include "../CPlayerInterface.h"
+#include "../CGameInfo.h"
+
+#include "../../lib/CConfigHandler.h"
+
+#include <SDL_events.h>
+
+std::queue<SDL_Event> SDLEventsQueue;
+boost::mutex eventsM;
+
+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(eventsM);
+	while(!SDLEventsQueue.empty())
+	{
+		GH.events().allowEventHandling(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 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)
+	{
+		CCS->curh->cursorMove(ev.motion.x, ev.motion.y);
+	}
+
+	{
+		boost::unique_lock<boost::mutex> lock(eventsM);
+		SDLEventsQueue.push(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;
+}

+ 59 - 0
client/eventsSDL/InputHandler.h

@@ -0,0 +1,59 @@
+/*
+* 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
+{
+	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();
+
+	void fetchEvents();
+	void processEvents();
+
+	void fakeMoveCursor(float dx, float dy);
+	void startTextInput(const Rect & where);
+	void stopTextInput();
+	bool isMouseButtonPressed(MouseButton button) const;
+	void pushUserEvent(EUserEvent usercode, void * userdata);
+
+	const Point & getCursorPosition() const;
+
+	/// returns true if chosen keyboard key is currently pressed down
+	bool isKeyboardAltDown() const;
+	bool isKeyboardCtrlDown() const;
+	bool isKeyboardShiftDown() const;
+};

+ 76 - 0
client/eventsSDL/InputSourceKeyboard.cpp

@@ -0,0 +1,76 @@
+/*
+* 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>
+
+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);
+}

+ 20 - 0
client/eventsSDL/InputSourceKeyboard.h

@@ -0,0 +1,20 @@
+/*
+* 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 InputSourceKeyboard
+{
+public:
+	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;
+	}
+}

+ 24 - 0
client/eventsSDL/InputSourceMouse.h

@@ -0,0 +1,24 @@
+/*
+* 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 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
+}

+ 28 - 0
client/eventsSDL/InputSourceText.h

@@ -0,0 +1,28 @@
+/*
+* 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 InputSourceText
+{
+public:
+	void handleEventTextInput(const SDL_TextInputEvent & current);
+	void handleEventTextEditing(const SDL_TextEditingEvent & current);
+
+	void startTextInput(const Rect & where);
+	void stopTextInput();
+};

+ 128 - 0
client/eventsSDL/InputSourceTouch.cpp

@@ -0,0 +1,128 @@
+/*
+* 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 <SDL_events.h>
+#include <SDL_render.h>
+
+InputSourceTouch::InputSourceTouch()
+	: multifinger(false)
+	, isPointerRelativeMode(settings["general"]["userRelativePointer"].Bool())
+{
+}
+
+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);
+}

+ 36 - 0
client/eventsSDL/InputSourceTouch.h

@@ -0,0 +1,36 @@
+/*
+* 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 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;
+	}
+}

+ 19 - 0
client/eventsSDL/UserEventHandler.h

@@ -0,0 +1,19 @@
+/*
+* 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 UserEventHandler
+{
+public:
+	void handleUserEvent(const SDL_UserEvent & current);
+};

+ 26 - 397
client/gui/CGuiHandler.cpp

@@ -17,6 +17,7 @@
 #include "FramerateManager.h"
 #include "WindowHandler.h"
 #include "EventDispatcher.h"
+#include "../eventsSDL/InputHandler.h"
 
 #include "../CGameInfo.h"
 #include "../render/Colors.h"
@@ -30,17 +31,6 @@
 #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;
 
@@ -80,382 +70,38 @@ SSetCaptureState::~SSetCaptureState()
 
 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::handleEvents()
 {
-	eventDispatcher().dispatchTimer(framerateManager().getElapsedMilliseconds());
+	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())
-	{
-		eventDispatcher().allowEventHandling(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 )
-{
-	switch (current.type)
-	{
-		case SDL_KEYDOWN:
-			return handleEventKeyDown(current);
-		case SDL_KEYUP:
-			return handleEventKeyUp(current);
-		case SDL_MOUSEMOTION:
-			return handleEventMouseMotion(current);
-		case SDL_MOUSEBUTTONDOWN:
-			return handleEventMouseButtonDown(current);
-		case SDL_MOUSEWHEEL:
-			return handleEventMouseWheel(current);
-		case SDL_TEXTINPUT:
-			return handleEventTextInput(current);
-		case SDL_TEXTEDITING:
-			return handleEventTextEditing(current);
-		case SDL_MOUSEBUTTONUP:
-			return handleEventMouseButtonUp(current);
-		case SDL_FINGERMOTION:
-			return handleEventFingerMotion(current);
-		case SDL_FINGERDOWN:
-			return handleEventFingerDown(current);
-		case SDL_FINGERUP:
-			return handleEventFingerUp(current);
-	}
-}
-
-void CGuiHandler::handleEventKeyDown(SDL_Event & current)
-{
-	SDL_KeyboardEvent key = current.key;
-
-	if(key.repeat != 0)
-		return; // ignore periodic event resends
-
-	assert(key.state == SDL_PRESSED);
-
-	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;
-
-			default:
-				break;
-		}
-		return;
-	}
-
-	auto shortcutsVector = shortcutsHandler().translateKeycode(key.keysym.sym);
-
-	eventDispatcher().dispatchShortcutPressed(shortcutsVector);
-}
-
-void CGuiHandler::handleEventKeyUp(SDL_Event & current)
-{
-	SDL_KeyboardEvent key = current.key;
-
-	if(key.repeat != 0)
-		return; // ignore periodic event resends
-
-	assert(key.state == SDL_RELEASED);
-
-	auto shortcutsVector = shortcutsHandler().translateKeycode(key.keysym.sym);
-
-	eventDispatcher().dispatchShortcutReleased(shortcutsVector);
-}
-
-void CGuiHandler::handleEventMouseMotion(SDL_Event & current)
-{
-	eventDispatcher().dispatchMouseMoved(Point(current.motion.x, current.motion.y));
-}
-
-void CGuiHandler::handleEventMouseButtonDown(SDL_Event & current)
-{
-	switch(current.button.button)
-	{
-		case SDL_BUTTON_LEFT:
-			if (current.button.clicks > 1)
-				eventDispatcher().dispatchMouseDoubleClick(Point(current.button.x, current.button.y));
-			else
-				eventDispatcher().dispatchMouseButtonPressed(MouseButton::LEFT, Point(current.button.x, current.button.y));
-			break;
-		case SDL_BUTTON_RIGHT:
-			eventDispatcher().dispatchMouseButtonPressed(MouseButton::RIGHT, Point(current.button.x, current.button.y));
-			break;
-		case SDL_BUTTON_MIDDLE:
-			eventDispatcher().dispatchMouseButtonPressed(MouseButton::MIDDLE, Point(current.button.x, current.button.y));
-			break;
-	}
-}
-
-void CGuiHandler::handleEventMouseWheel(SDL_Event & current)
-{
-	// SDL doesn't have the proper values for mouse positions on SDL_MOUSEWHEEL, refetch them
-	int x = 0, y = 0;
-	SDL_GetMouseState(&x, &y);
-
-	eventDispatcher().dispatchMouseScrolled(Point(current.wheel.x, current.wheel.y), Point(x, y));
-}
-
-void CGuiHandler::handleEventTextInput(SDL_Event & current)
-{
-	eventDispatcher().dispatchTextInput(current.text.text);
-}
-
-void CGuiHandler::handleEventTextEditing(SDL_Event & current)
-{
-	eventDispatcher().dispatchTextEditing(current.text.text);
-}
-
-void CGuiHandler::handleEventMouseButtonUp(SDL_Event & current)
-{
-	if(!multifinger)
-	{
-		switch(current.button.button)
-		{
-			case SDL_BUTTON_LEFT:
-				eventDispatcher().dispatchMouseButtonReleased(MouseButton::LEFT, Point(current.button.x, current.button.y));
-				break;
-			case SDL_BUTTON_RIGHT:
-				eventDispatcher().dispatchMouseButtonReleased(MouseButton::RIGHT, Point(current.button.x, current.button.y));
-				break;
-			case SDL_BUTTON_MIDDLE:
-				eventDispatcher().dispatchMouseButtonReleased(MouseButton::MIDDLE, Point(current.button.x, current.button.y));
-				break;
-		}
-	}
-}
-
-void CGuiHandler::handleEventFingerMotion(SDL_Event & current)
-{
-	if(isPointerRelativeMode)
-	{
-		fakeMoveCursor(current.tfinger.dx, current.tfinger.dy);
-	}
-}
-	
-void CGuiHandler::handleEventFingerDown(SDL_Event & current)
-{
-	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);
-
-		eventDispatcher().dispatchMouseMoved(Point(current.button.x, current.button.y));
-		eventDispatcher().dispatchMouseButtonPressed(MouseButton::RIGHT, Point(current.button.x, current.button.y));
-	}
-#endif //VCMI_IOS
-}
-
-void CGuiHandler::handleEventFingerUp(SDL_Event & current)
-{
-#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);
-		eventDispatcher().dispatchMouseMoved(Point(current.button.x, current.button.y));
-		eventDispatcher().dispatchMouseButtonReleased(MouseButton::RIGHT, Point(current.button.x, current.button.y));
-		multifinger = fingerCount != 0;
-	}
-#endif //VCMI_IOS
+	input().stopTextInput();
 }
 
 void CGuiHandler::renderFrame()
@@ -493,14 +139,12 @@ void CGuiHandler::renderFrame()
 		windows().onFrameRendered();
 	}
 
-	framerateManager().framerateDelay(); // holds a constant FPS
+	framerate().framerateDelay(); // holds a constant FPS
 }
 
 CGuiHandler::CGuiHandler()
 	: defActionsDef(0)
 	, captureChildren(false)
-	, multifinger(false)
-	, mouseButtonsMask(0)
 	, curInt(nullptr)
 	, fakeStatusBar(std::make_shared<EmptyStatusBar>())
 	, terminate_cond (new CondSh<bool>(false))
@@ -512,50 +156,41 @@ 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];
+	return inputHandlerInstance->isKeyboardShiftDown();
 }
 
 void CGuiHandler::breakEventHandling()
 {
-	eventDispatcher().allowEventHandling(false);
+	events().allowEventHandling(false);
 }
 
 const Point & CGuiHandler::getCursorPosition() const
 {
-	return cursorPosition;
+	return inputHandlerInstance->getCursorPosition();
 }
 
 Point CGuiHandler::screenDimensions() const
@@ -565,19 +200,12 @@ Point CGuiHandler::screenDimensions() const
 
 bool CGuiHandler::isMouseButtonPressed() const
 {
-	return mouseButtonsMask > 0;
+	return isMouseButtonPressed(MouseButton::LEFT) || isMouseButtonPressed(MouseButton::MIDDLE) || isMouseButtonPressed(MouseButton::RIGHT);
 }
 
 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()
@@ -585,7 +213,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));
 }
 
@@ -596,16 +224,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()
@@ -613,11 +237,16 @@ IScreenHandler & CGuiHandler::screenHandler()
 	return *screenHandlerInstance;
 }
 
-EventDispatcher & CGuiHandler::eventDispatcher()
+EventDispatcher & CGuiHandler::events()
 {
 	return *eventDispatcherInstance;
 }
 
+InputHandler & CGuiHandler::input()
+{
+	return *inputHandlerInstance;
+}
+
 WindowHandler & CGuiHandler::windows()
 {
 	assert(windowHandlerInstance);

+ 13 - 40
client/gui/CGuiHandler.h

@@ -31,18 +31,17 @@ class IShowActivatable;
 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
@@ -55,41 +54,22 @@ 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::unique_ptr<IScreenHandler> screenHandlerInstance;
 	std::unique_ptr<FramerateManager> framerateManagerInstance;
 	std::unique_ptr<EventDispatcher> eventDispatcherInstance;
-
-	void handleCurrentEvent(SDL_Event &current);
-	void convertTouchToMouse(SDL_Event * current);
-	void fakeMoveCursor(float dx, float dy);
-	void fakeMouseButtonEventRelativeMode(bool down, bool right);
-
-	void handleEventKeyDown(SDL_Event & current);
-	void handleEventKeyUp(SDL_Event & current);
-	void handleEventMouseMotion(SDL_Event & current);
-	void handleEventMouseButtonDown(SDL_Event & current);
-	void handleEventMouseWheel(SDL_Event & current);
-	void handleEventTextInput(SDL_Event & current);
-	void handleEventTextEditing(SDL_Event & current);
-	void handleEventMouseButtonUp(SDL_Event & current);
-	void handleEventFingerMotion(SDL_Event & current);
-	void handleEventFingerDown(SDL_Event & current);
-	void handleEventFingerUp(SDL_Event & current);
+	std::unique_ptr<InputHandler> inputHandlerInstance;
 
 public:
-
 	/// returns current position of mouse cursor, relative to vcmi window
 	const Point & getCursorPosition() const;
 
-	ShortcutHandler & shortcutsHandler();
-	FramerateManager & framerateManager();
-	EventDispatcher & eventDispatcher();
+	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%
@@ -109,9 +89,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();
@@ -124,10 +101,6 @@ public:
 
 	IUpdateable *curInt;
 
-	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
@@ -146,9 +119,9 @@ public:
 	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
 };

+ 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)

+ 2 - 2
client/gui/EventsReceiver.cpp

@@ -51,14 +51,14 @@ void AEventsReceiver::activateEvents(ui16 what)
 	assert((what & GENERAL) || (activeState & GENERAL));
 
 	activeState |= GENERAL;
-	GH.eventDispatcher().handleElementActivate(this, what);
+	GH.events().handleElementActivate(this, what);
 }
 
 void AEventsReceiver::deactivateEvents(ui16 what)
 {
 	if (what & GENERAL)
 		activeState &= ~GENERAL;
-	GH.eventDispatcher().handleElementDeActivate(this, what & activeState);
+	GH.events().handleElementDeActivate(this, what & activeState);
 }
 
 void AEventsReceiver::click(MouseButton btn, tribool down, bool previousState)

+ 1 - 1
client/gui/InterfaceObjectConfigurable.cpp

@@ -224,7 +224,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 - 1
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);
 

+ 2 - 2
client/mapView/MapViewActions.cpp

@@ -98,8 +98,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"