1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-11-28 08:48:48 +02:00

Merge pull request #2168 from IvanSavenko/gui_handler_event_handling_refactoring

Event handling refactoring
This commit is contained in:
Ivan Savenko 2023-05-23 15:08:10 +03:00 committed by GitHub
commit 2c3e8c3390
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
59 changed files with 1647 additions and 1150 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,23 @@
/*
* InputSourceKeyboard.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
struct SDL_KeyboardEvent;
/// Class that handles keyboard input from SDL events
class InputSourceKeyboard
{
public:
InputSourceKeyboard();
void handleEventKeyDown(const SDL_KeyboardEvent & current);
void handleEventKeyUp(const SDL_KeyboardEvent & current);
};

View File

@ -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;
}
}

View File

@ -0,0 +1,25 @@
/*
* InputSourceMouse.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
struct SDL_MouseWheelEvent;
struct SDL_MouseMotionEvent;
struct SDL_MouseButtonEvent;
/// Class that handles mouse input from SDL events
class InputSourceMouse
{
public:
void handleEventMouseMotion(const SDL_MouseMotionEvent & current);
void handleEventMouseButtonDown(const SDL_MouseButtonEvent & current);
void handleEventMouseWheel(const SDL_MouseWheelEvent & current);
void handleEventMouseButtonUp(const SDL_MouseButtonEvent & current);
};

View File

@ -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
}

View File

@ -0,0 +1,29 @@
/*
* InputSourceText.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
VCMI_LIB_NAMESPACE_BEGIN
class Rect;
VCMI_LIB_NAMESPACE_END
struct SDL_TextEditingEvent;
struct SDL_TextInputEvent;
/// Class that handles text input (e.g. IME or direct input from physical keyboard) from SDL events
class InputSourceText
{
public:
void handleEventTextInput(const SDL_TextInputEvent & current);
void handleEventTextEditing(const SDL_TextEditingEvent & current);
void startTextInput(const Rect & where);
void stopTextInput();
};

View File

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

View File

@ -0,0 +1,37 @@
/*
* InputSourceTouch.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
VCMI_LIB_NAMESPACE_BEGIN
class Point;
VCMI_LIB_NAMESPACE_END
struct SDL_TouchFingerEvent;
/// Class that handles touchscreen input from SDL events
class InputSourceTouch
{
bool multifinger;
bool isPointerRelativeMode;
/// moves mouse pointer into specified position inside vcmi window
void moveCursorToPosition(const Point & position);
Point convertTouchToMouse(const SDL_TouchFingerEvent & current);
void fakeMouseButtonEventRelativeMode(bool down, bool right);
public:
InputSourceTouch();
void handleEventFingerMotion(const SDL_TouchFingerEvent & current);
void handleEventFingerDown(const SDL_TouchFingerEvent & current);
void handleEventFingerUp(const SDL_TouchFingerEvent & current);
};

View File

@ -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:

View File

@ -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;
}
}

View File

@ -0,0 +1,20 @@
/*
* EventHandlerSDLUser.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
struct SDL_UserEvent;
/// Class for handling events of type SDL_UserEvent
class UserEventHandler
{
public:
void handleUserEvent(const SDL_UserEvent & current);
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,74 @@
/*
* EventsReceiver.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "EventsReceiver.h"
#include "MouseButton.h"
#include "CGuiHandler.h"
#include "EventDispatcher.h"
AEventsReceiver::AEventsReceiver()
: activeState(0)
, hoveredState(false)
, strongInterestState(false)
{
}
bool AEventsReceiver::isHovered() const
{
return hoveredState;
}
bool AEventsReceiver::isActive() const
{
return activeState;
}
bool AEventsReceiver::isMouseButtonPressed(MouseButton btn) const
{
return currentMouseState.count(btn) ? currentMouseState.at(btn) : false;
}
void AEventsReceiver::setMoveEventStrongInterest(bool on)
{
strongInterestState = on;
}
void AEventsReceiver::activateEvents(ui16 what)
{
assert((what & GENERAL) || (activeState & GENERAL));
activeState |= GENERAL;
GH.events().activateElement(this, what);
}
void AEventsReceiver::deactivateEvents(ui16 what)
{
if (what & GENERAL)
activeState &= ~GENERAL;
GH.events().deactivateElement(this, what & activeState);
}
void AEventsReceiver::click(MouseButton btn, tribool down, bool previousState)
{
switch(btn)
{
default:
case MouseButton::LEFT:
clickLeft(down, previousState);
break;
case MouseButton::MIDDLE:
clickMiddle(down, previousState);
break;
case MouseButton::RIGHT:
clickRight(down, previousState);
break;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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