From 5e86b00dda9c338806b046cc4047c2b7d50def45 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 18 May 2023 20:32:29 +0300 Subject: [PATCH] Moved input handling from GuiHandler to set of classes in eventsSDL dir --- client/CMT.cpp | 155 +------ client/CMakeLists.txt | 20 +- client/CPlayerInterface.cpp | 2 +- client/CServerHandler.cpp | 2 +- client/CVideoHandler.cpp | 2 +- client/eventsSDL/InputHandler.cpp | 250 +++++++++++ client/eventsSDL/InputHandler.h | 59 +++ client/eventsSDL/InputSourceKeyboard.cpp | 76 ++++ client/eventsSDL/InputSourceKeyboard.h | 20 + client/eventsSDL/InputSourceMouse.cpp | 72 +++ client/eventsSDL/InputSourceMouse.h | 24 + client/eventsSDL/InputSourceText.cpp | 92 ++++ client/eventsSDL/InputSourceText.h | 28 ++ client/eventsSDL/InputSourceTouch.cpp | 128 ++++++ client/eventsSDL/InputSourceTouch.h | 36 ++ .../NotificationHandler.cpp | 4 +- .../{gui => eventsSDL}/NotificationHandler.h | 0 client/eventsSDL/UserEventHandler.cpp | 87 ++++ client/eventsSDL/UserEventHandler.h | 19 + client/gui/CGuiHandler.cpp | 423 ++---------------- client/gui/CGuiHandler.h | 53 +-- client/gui/CursorHandler.cpp | 2 +- client/gui/EventsReceiver.cpp | 4 +- client/gui/InterfaceObjectConfigurable.cpp | 2 +- client/mainmenu/CMainMenu.cpp | 2 +- client/mapView/MapViewActions.cpp | 4 +- client/renderSDL/ScreenHandler.cpp | 2 +- 27 files changed, 968 insertions(+), 600 deletions(-) create mode 100644 client/eventsSDL/InputHandler.cpp create mode 100644 client/eventsSDL/InputHandler.h create mode 100644 client/eventsSDL/InputSourceKeyboard.cpp create mode 100644 client/eventsSDL/InputSourceKeyboard.h create mode 100644 client/eventsSDL/InputSourceMouse.cpp create mode 100644 client/eventsSDL/InputSourceMouse.h create mode 100644 client/eventsSDL/InputSourceText.cpp create mode 100644 client/eventsSDL/InputSourceText.h create mode 100644 client/eventsSDL/InputSourceTouch.cpp create mode 100644 client/eventsSDL/InputSourceTouch.h rename client/{gui => eventsSDL}/NotificationHandler.cpp (100%) rename client/{gui => eventsSDL}/NotificationHandler.h (100%) create mode 100644 client/eventsSDL/UserEventHandler.cpp create mode 100644 client/eventsSDL/UserEventHandler.h diff --git a/client/CMT.cpp b/client/CMT.cpp index d1beb36e3..41992b454 100644 --- a/client/CMT.cpp +++ b/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 #include -#include -#include -#include - -#ifdef VCMI_WINDOWS -#include -#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 inGuiThread; -std::queue 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(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(reinterpret_cast(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(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 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 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 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(); } diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 1d2453b82..44fe19d93 100644 --- a/client/CMakeLists.txt +++ b/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 "$" diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 1e6174f49..53f861b9b 100644 --- a/client/CPlayerInterface.cpp +++ b/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 diff --git a/client/CServerHandler.cpp b/client/CServerHandler.cpp index 32d5d6f41..cbafa8fad 100644 --- a/client/CServerHandler.cpp +++ b/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 { diff --git a/client/CVideoHandler.cpp b/client/CVideoHandler.cpp index 3864ce125..f3da0d4a1 100644 --- a/client/CVideoHandler.cpp +++ b/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 ) { diff --git a/client/eventsSDL/InputHandler.cpp b/client/eventsSDL/InputHandler.cpp new file mode 100644 index 000000000..f88fffa2e --- /dev/null +++ b/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 + +std::queue SDLEventsQueue; +boost::mutex eventsM; + +InputHandler::InputHandler() + : mouseHandler(std::make_unique()) + , keyboardHandler(std::make_unique()) + , fingerHandler(std::make_unique()) + , textHandler(std::make_unique()) + , userHandler(std::make_unique()) + , 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 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 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 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(MouseButton::LEFT) == SDL_BUTTON_LEFT, "mismatch between VCMI and SDL enum!"); + static_assert(static_cast(MouseButton::MIDDLE) == SDL_BUTTON_MIDDLE, "mismatch between VCMI and SDL enum!"); + static_assert(static_cast(MouseButton::RIGHT) == SDL_BUTTON_RIGHT, "mismatch between VCMI and SDL enum!"); + static_assert(static_cast(MouseButton::EXTRA1) == SDL_BUTTON_X1, "mismatch between VCMI and SDL enum!"); + static_assert(static_cast(MouseButton::EXTRA2) == SDL_BUTTON_X2, "mismatch between VCMI and SDL enum!"); + + uint32_t index = static_cast(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(usercode); + event.user.data1 = userdata; + SDL_PushEvent(&event); +} + +const Point & InputHandler::getCursorPosition() const +{ + return cursorPosition; +} diff --git a/client/eventsSDL/InputHandler.h b/client/eventsSDL/InputHandler.h new file mode 100644 index 000000000..4febec840 --- /dev/null +++ b/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 mouseHandler; + std::unique_ptr keyboardHandler; + std::unique_ptr fingerHandler; + std::unique_ptr textHandler; + std::unique_ptr 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; +}; diff --git a/client/eventsSDL/InputSourceKeyboard.cpp b/client/eventsSDL/InputSourceKeyboard.cpp new file mode 100644 index 000000000..9c74e9647 --- /dev/null +++ b/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 + +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); +} diff --git a/client/eventsSDL/InputSourceKeyboard.h b/client/eventsSDL/InputSourceKeyboard.h new file mode 100644 index 000000000..3006a53ce --- /dev/null +++ b/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); +}; diff --git a/client/eventsSDL/InputSourceMouse.cpp b/client/eventsSDL/InputSourceMouse.cpp new file mode 100644 index 000000000..8ed85ad88 --- /dev/null +++ b/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 + +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; + } +} diff --git a/client/eventsSDL/InputSourceMouse.h b/client/eventsSDL/InputSourceMouse.h new file mode 100644 index 000000000..f30609aa1 --- /dev/null +++ b/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); +}; diff --git a/client/eventsSDL/InputSourceText.cpp b/client/eventsSDL/InputSourceText.cpp new file mode 100644 index 000000000..8d4a4a4a8 --- /dev/null +++ b/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 +#include + +#ifdef VCMI_APPLE +# include +#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 +} diff --git a/client/eventsSDL/InputSourceText.h b/client/eventsSDL/InputSourceText.h new file mode 100644 index 000000000..02473b3ec --- /dev/null +++ b/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(); +}; diff --git a/client/eventsSDL/InputSourceTouch.cpp b/client/eventsSDL/InputSourceTouch.cpp new file mode 100644 index 000000000..88525bcab --- /dev/null +++ b/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 +#include + +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); +} diff --git a/client/eventsSDL/InputSourceTouch.h b/client/eventsSDL/InputSourceTouch.h new file mode 100644 index 000000000..07017ef70 --- /dev/null +++ b/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); +}; diff --git a/client/gui/NotificationHandler.cpp b/client/eventsSDL/NotificationHandler.cpp similarity index 100% rename from client/gui/NotificationHandler.cpp rename to client/eventsSDL/NotificationHandler.cpp index 14aa36ecf..300dc344f 100644 --- a/client/gui/NotificationHandler.cpp +++ b/client/eventsSDL/NotificationHandler.cpp @@ -10,11 +10,11 @@ #include "StdInc.h" #include "NotificationHandler.h" -#include -#include #if defined(VCMI_WINDOWS) #include +#include +#include #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers // Windows Header Files: diff --git a/client/gui/NotificationHandler.h b/client/eventsSDL/NotificationHandler.h similarity index 100% rename from client/gui/NotificationHandler.h rename to client/eventsSDL/NotificationHandler.h diff --git a/client/eventsSDL/UserEventHandler.cpp b/client/eventsSDL/UserEventHandler.cpp new file mode 100644 index 000000000..53242ce8b --- /dev/null +++ b/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 + +void UserEventHandler::handleUserEvent(const SDL_UserEvent & user) +{ + switch(static_cast(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(reinterpret_cast(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(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 lock(*CPlayerInterface::pim); + GH.onScreenResize(); + break; + } + default: + logGlobal->error("Unknown user event. Code %d", user.code); + break; + } +} diff --git a/client/eventsSDL/UserEventHandler.h b/client/eventsSDL/UserEventHandler.h new file mode 100644 index 000000000..3fdd0a2ee --- /dev/null +++ b/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); +}; diff --git a/client/gui/CGuiHandler.cpp b/client/gui/CGuiHandler.cpp index b5640ca6d..6de8b5f2d 100644 --- a/client/gui/CGuiHandler.cpp +++ b/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 -#include -#include -#include - -#ifdef VCMI_APPLE -#include -#endif - -#ifdef VCMI_IOS -#include "ios/utils.h" -#endif CGuiHandler GH; @@ -80,382 +70,38 @@ SSetCaptureState::~SSetCaptureState() void CGuiHandler::init() { + inputHandlerInstance = std::make_unique(); eventDispatcherInstance = std::make_unique(); windowHandlerInstance = std::make_unique(); screenHandlerInstance = std::make_unique(); shortcutsHandlerInstance = std::make_unique(); framerateManagerInstance = std::make_unique(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 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(¤t); - - 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(¤t); - 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()) , terminate_cond (new CondSh(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(MouseButton::LEFT) == SDL_BUTTON_LEFT, "mismatch between VCMI and SDL enum!"); - static_assert(static_cast(MouseButton::MIDDLE) == SDL_BUTTON_MIDDLE, "mismatch between VCMI and SDL enum!"); - static_assert(static_cast(MouseButton::RIGHT) == SDL_BUTTON_RIGHT, "mismatch between VCMI and SDL enum!"); - static_assert(static_cast(MouseButton::EXTRA1) == SDL_BUTTON_X1, "mismatch between VCMI and SDL enum!"); - static_assert(static_cast(MouseButton::EXTRA2) == SDL_BUTTON_X2, "mismatch between VCMI and SDL enum!"); - - uint32_t index = static_cast(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(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); diff --git a/client/gui/CGuiHandler.h b/client/gui/CGuiHandler.h index c3765d150..a8cbb7829 100644 --- a/client/gui/CGuiHandler.h +++ b/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 currentStatusBar; - Point cursorPosition; - uint32_t mouseButtonsMask; - std::unique_ptr shortcutsHandlerInstance; std::unique_ptr windowHandlerInstance; std::unique_ptr screenHandlerInstance; std::unique_ptr framerateManagerInstance; std::unique_ptr eventDispatcherInstance; - - void handleCurrentEvent(SDL_Event ¤t); - 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 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 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 * terminate_cond; // confirm termination }; diff --git a/client/gui/CursorHandler.cpp b/client/gui/CursorHandler.cpp index 29fe7f5d3..06e0f52ad 100644 --- a/client/gui/CursorHandler.cpp +++ b/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) diff --git a/client/gui/EventsReceiver.cpp b/client/gui/EventsReceiver.cpp index 24eb3ac59..2dcd4fc0a 100644 --- a/client/gui/EventsReceiver.cpp +++ b/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) diff --git a/client/gui/InterfaceObjectConfigurable.cpp b/client/gui/InterfaceObjectConfigurable.cpp index 5196df21c..155606fbd 100644 --- a/client/gui/InterfaceObjectConfigurable.cpp +++ b/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;; diff --git a/client/mainmenu/CMainMenu.cpp b/client/mainmenu/CMainMenu.cpp index 3a0111f8b..6d2cda6ff 100644 --- a/client/mainmenu/CMainMenu.cpp +++ b/client/mainmenu/CMainMenu.cpp @@ -233,7 +233,7 @@ std::shared_ptr 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(Point(posx, posy), button["name"].String(), help, command, shortcut); diff --git a/client/mapView/MapViewActions.cpp b/client/mapView/MapViewActions.cpp index 024cd3588..3750dfbe0 100644 --- a/client/mapView/MapViewActions.cpp +++ b/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) diff --git a/client/renderSDL/ScreenHandler.cpp b/client/renderSDL/ScreenHandler.cpp index 9709575eb..fa2779601 100644 --- a/client/renderSDL/ScreenHandler.cpp +++ b/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"