#include "StdInc.h" #include "CGuiHandler.h" #include "../lib/CondSh.h" #include #include "CIntObject.h" #include "CCursorHandler.h" #include "../CGameInfo.h" #include "../../lib/CThreadHelper.h" #include "../../lib/CConfigHandler.h" #include "../CMT.h" #include "../CPlayerInterface.h" extern std::queue events; extern boost::mutex eventsM; CondSh CGuiHandler::terminate_cond(false); boost::thread_specific_ptr inGuiThread; SObjectConstruction::SObjectConstruction(CIntObject *obj) :myObj(obj) { GH.createdObj.push_front(obj); GH.captureChildren = true; } SObjectConstruction::~SObjectConstruction() { assert(GH.createdObj.size()); assert(GH.createdObj.front() == myObj); GH.createdObj.pop_front(); GH.captureChildren = GH.createdObj.size(); } SSetCaptureState::SSetCaptureState(bool allow, ui8 actions) { previousCapture = GH.captureChildren; GH.captureChildren = false; prevActions = GH.defActionsDef; GH.defActionsDef = actions; } SSetCaptureState::~SSetCaptureState() { GH.captureChildren = previousCapture; GH.defActionsDef = prevActions; } static inline void processList(const ui16 mask, const ui16 flag, std::list *lst, std::function *)> cb) { if (mask & flag) cb(lst); } void CGuiHandler::processLists(const ui16 activityFlag, std::function *)> cb) { processList(CIntObject::LCLICK,activityFlag,&lclickable,cb); processList(CIntObject::RCLICK,activityFlag,&rclickable,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::handleElementActivate(CIntObject * elem, ui16 activityFlag) { processLists(activityFlag,[&](std::list * lst){ lst->push_front(elem); }); elem->active_m |= activityFlag; } void CGuiHandler::handleElementDeActivate(CIntObject * elem, ui16 activityFlag) { processLists(activityFlag,[&](std::list * lst){ auto hlp = std::find(lst->begin(),lst->end(),elem); assert(hlp != lst->end()); lst->erase(hlp); }); elem->active_m &= ~activityFlag; } void CGuiHandler::popInt(IShowActivatable *top) { assert(listInt.front() == top); top->deactivate(); listInt.pop_front(); objsToBlit -= top; if(!listInt.empty()) listInt.front()->activate(); totalRedraw(); } void CGuiHandler::popIntTotally(IShowActivatable *top) { assert(listInt.front() == top); popInt(top); delete top; fakeMouseMove(); } void CGuiHandler::pushInt(IShowActivatable *newInt) { assert(newInt); assert(boost::range::find(listInt, newInt) == listInt.end()); // do not add same object twice //a new interface will be present, we'll need to use buffer surface (unless it's advmapint that will alter screenBuf on activate anyway) screenBuf = screen2; if(!listInt.empty()) listInt.front()->deactivate(); listInt.push_front(newInt); newInt->activate(); objsToBlit.push_back(newInt); totalRedraw(); } void CGuiHandler::popInts(int howMany) { if(!howMany) return; //senseless but who knows... assert(listInt.size() >= howMany); listInt.front()->deactivate(); for(int i=0; i < howMany; i++) { objsToBlit -= listInt.front(); delete listInt.front(); listInt.pop_front(); } if(!listInt.empty()) { listInt.front()->activate(); totalRedraw(); } fakeMouseMove(); } IShowActivatable * CGuiHandler::topInt() { if(listInt.empty()) return nullptr; else return listInt.front(); } void CGuiHandler::totalRedraw() { for(auto & elem : objsToBlit) elem->showAll(screen2); blitAt(screen2,0,0,screen); } void CGuiHandler::updateTime() { int ms = mainFPSmng->getElapsedMilliseconds(); std::list hlp = timeinterested; for (auto & elem : hlp) { if(!vstd::contains(timeinterested,elem)) continue; (elem)->onTimer(ms); } } void CGuiHandler::handleEvents() { //player interface may want special event handling if(nullptr != LOCPLINT && LOCPLINT->capturedAllEvents()) return; boost::unique_lock lock(eventsM); while(!events.empty()) { SDL_Event ev = events.front(); events.pop(); this->handleEvent(&ev); } } void CGuiHandler::handleEvent(SDL_Event *sEvent) { current = sEvent; bool prev; if (sEvent->type==SDL_KEYDOWN || sEvent->type==SDL_KEYUP) { SDL_KeyboardEvent key = sEvent->key; //translate numpad keys if(key.keysym.sym == SDLK_KP_ENTER) { key.keysym.sym = SDLK_RETURN; key.keysym.scancode = SDL_SCANCODE_RETURN; } bool keysCaptured = false; for(auto i=keyinterested.begin(); i != keyinterested.end() && current; i++) { if((*i)->captureThisEvent(key)) { keysCaptured = true; break; } } std::list miCopy = keyinterested; for(auto i=miCopy.begin(); i != miCopy.end() && current; i++) if(vstd::contains(keyinterested,*i) && (!keysCaptured || (*i)->captureThisEvent(key))) (**i).keyPressed(key); } else if(sEvent->type==SDL_MOUSEMOTION) { CCS->curh->cursorMove(sEvent->motion.x, sEvent->motion.y); handleMouseMotion(sEvent); } else if (sEvent->type==SDL_MOUSEBUTTONDOWN) { if(sEvent->button.button == SDL_BUTTON_LEFT) { if(lastClick == sEvent->motion && (SDL_GetTicks() - lastClickTime) < 300) { std::list hlp = doubleClickInterested; for(auto i=hlp.begin(); i != hlp.end() && current; i++) { if(!vstd::contains(doubleClickInterested,*i)) continue; if (isItIn(&(*i)->pos,sEvent->motion.x,sEvent->motion.y)) { (*i)->onDoubleClick(); } } } lastClick = sEvent->motion; lastClickTime = SDL_GetTicks(); std::list hlp = lclickable; for(auto i=hlp.begin(); i != hlp.end() && current; i++) { if(!vstd::contains(lclickable,*i)) continue; if (isItIn(&(*i)->pos,sEvent->motion.x,sEvent->motion.y)) { prev = (*i)->pressedL; (*i)->pressedL = true; (*i)->clickLeft(true, prev); } } } else if (sEvent->button.button == SDL_BUTTON_RIGHT) { std::list hlp = rclickable; for(auto i=hlp.begin(); i != hlp.end() && current; i++) { if(!vstd::contains(rclickable,*i)) continue; if (isItIn(&(*i)->pos,sEvent->motion.x,sEvent->motion.y)) { prev = (*i)->pressedR; (*i)->pressedR = true; (*i)->clickRight(true, prev); } } } } else if (sEvent->type == SDL_MOUSEWHEEL) { std::list hlp = wheelInterested; for(auto i=hlp.begin(); i != hlp.end() && current; 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(sEvent->wheel.y < 0, isItIn(&(*i)->pos, x, y)); } } else if(sEvent->type == SDL_TEXTINPUT) { for(auto it : textInterested) { it->textInputed(sEvent->text); } } else if(sEvent->type == SDL_TEXTEDITING) { for(auto it : textInterested) { it->textEdited(sEvent->edit); } } //todo: muiltitouch else if ((sEvent->type==SDL_MOUSEBUTTONUP) && (sEvent->button.button == SDL_BUTTON_LEFT)) { std::list hlp = lclickable; for(auto i=hlp.begin(); i != hlp.end() && current; i++) { if(!vstd::contains(lclickable,*i)) continue; prev = (*i)->pressedL; (*i)->pressedL = false; if (isItIn(&(*i)->pos,sEvent->motion.x,sEvent->motion.y)) { (*i)->clickLeft(false, prev); } else (*i)->clickLeft(boost::logic::indeterminate, prev); } } else if ((sEvent->type==SDL_MOUSEBUTTONUP) && (sEvent->button.button == SDL_BUTTON_RIGHT)) { std::list hlp = rclickable; for(auto i=hlp.begin(); i != hlp.end() && current; i++) { if(!vstd::contains(rclickable,*i)) continue; prev = (*i)->pressedR; (*i)->pressedR = false; if (isItIn(&(*i)->pos,sEvent->motion.x,sEvent->motion.y)) { (*i)->clickRight(false, prev); } else (*i)->clickRight(boost::logic::indeterminate, prev); } } current = nullptr; } //event end void CGuiHandler::handleMouseMotion(SDL_Event *sEvent) { //sending active, hovered hoverable objects hover() call std::vector hlp; for(auto & elem : hoverable) { if (isItIn(&(elem)->pos,sEvent->motion.x,sEvent->motion.y)) { 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; } handleMoveInterested(sEvent->motion); } void CGuiHandler::simpleRedraw() { //update only top interface and draw background if(objsToBlit.size() > 1) blitAt(screen2,0,0,screen); //blit background if(!objsToBlit.empty()) objsToBlit.back()->show(screen); //blit active interface/window } void CGuiHandler::handleMoveInterested(const SDL_MouseMotionEvent & motion) { //sending active, MotionInterested objects mouseMoved() call std::list miCopy = motioninterested; for(auto & elem : miCopy) { if ((elem)->strongInterest || isItIn(&(elem)->pos, motion.x-1, motion.y-1)) //-1 offset to include lower bound, fixes bug #2476 { (elem)->mouseMoved(motion); } } } void CGuiHandler::fakeMouseMove() { SDL_Event evnt; SDL_MouseMotionEvent sme = {SDL_MOUSEMOTION, 0, 0, 0, 0, 0, 0, 0, 0}; int x, y; sme.state = SDL_GetMouseState(&x, &y); sme.x = x; sme.y = y; evnt.motion = sme; current = &evnt; handleMouseMotion(&evnt); } 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, // that will notify us about the ending game by setting terminate_cond flag. //in PreGame terminate_cond stay false bool acquiredTheLockOnPim = false; //for tracking whether pim mutex locking succeeded while(!terminate_cond.get() && !(acquiredTheLockOnPim = CPlayerInterface::pim->try_lock())) //try acquiring long until it succeeds or we are told to terminate boost::this_thread::sleep(boost::posix_time::milliseconds(15)); if(acquiredTheLockOnPim) { // If we are here, pim mutex has been successfully locked - let's store it in a safe RAII lock. boost::unique_lock un(*CPlayerInterface::pim, boost::adopt_lock); if(nullptr != curInt) curInt->update(); if (settings["general"]["showfps"].Bool()) drawFPSCounter(); // draw the mouse cursor and update the screen CCS->curh->render(); if(0 != SDL_RenderCopy(mainRenderer, screenTexture, nullptr, nullptr)) logGlobal->errorStream() << __FUNCTION__ << " SDL_RenderCopy " << SDL_GetError(); SDL_RenderPresent(mainRenderer); } mainFPSmng->framerateDelay(); // holds a constant FPS } CGuiHandler::CGuiHandler() : lastClick(-500, -500),lastClickTime(0), defActionsDef(0), captureChildren(false) { curInt = nullptr; current = nullptr; statusbar = nullptr; // Creates the FPS manager and sets the framerate to 48 which is doubled the value of the original Heroes 3 FPS rate mainFPSmng = new CFramerateManager(48); //do not init CFramerateManager here --AVS terminate_cond.set(false); } CGuiHandler::~CGuiHandler() { delete mainFPSmng; } void CGuiHandler::breakEventHandling() { current = nullptr; } void CGuiHandler::drawFPSCounter() { const static SDL_Color yellow = {255, 255, 0, 0}; static SDL_Rect overlay = { 0, 0, 64, 32}; Uint32 black = SDL_MapRGB(screen->format, 10, 10, 10); SDL_FillRect(screen, &overlay, black); std::string fps = boost::lexical_cast(mainFPSmng->fps); graphics->fonts[FONT_BIG]->renderTextLeft(screen, fps, yellow, Point(10, 10)); } SDL_Keycode CGuiHandler::arrowToNum(SDL_Keycode key) { switch(key) { case SDLK_DOWN: return SDLK_KP_2; case SDLK_UP: return SDLK_KP_8; case SDLK_LEFT: return SDLK_KP_4; case SDLK_RIGHT: return SDLK_KP_6; default: throw std::runtime_error("Wrong key!"); } } SDL_Keycode CGuiHandler::numToDigit(SDL_Keycode key) { #define REMOVE_KP(keyName) case SDLK_KP_ ## keyName : return SDLK_ ## keyName; switch(key) { REMOVE_KP(0) REMOVE_KP(1) REMOVE_KP(2) REMOVE_KP(3) REMOVE_KP(4) REMOVE_KP(5) REMOVE_KP(6) REMOVE_KP(7) REMOVE_KP(8) REMOVE_KP(9) REMOVE_KP(PERIOD) REMOVE_KP(MINUS) REMOVE_KP(PLUS) REMOVE_KP(EQUALS) case SDLK_KP_MULTIPLY: return SDLK_ASTERISK; case SDLK_KP_DIVIDE: return SDLK_SLASH; case SDLK_KP_ENTER: return SDLK_RETURN; default: return SDLK_UNKNOWN; } #undef REMOVE_KP } bool CGuiHandler::isNumKey(SDL_Keycode key, bool number) { if(number) return key >= SDLK_KP_1 && key <= SDLK_KP_0; else return (key >= SDLK_KP_1 && key <= SDLK_KP_0) || key == SDLK_KP_MINUS || key == SDLK_KP_PLUS || key == SDLK_KP_EQUALS; } bool CGuiHandler::isArrowKey(SDL_Keycode key) { return key == SDLK_UP || key == SDLK_DOWN || key == SDLK_LEFT || key == SDLK_RIGHT; } bool CGuiHandler::amIGuiThread() { return inGuiThread.get() && *inGuiThread; } void CGuiHandler::pushSDLEvent(int type, int usercode) { SDL_Event event; event.type = type; event.user.code = usercode; // not necessarily used SDL_PushEvent(&event); } CFramerateManager::CFramerateManager(int rate) { this->rate = rate; this->rateticks = (1000.0 / rate); this->fps = 0; this->accumulatedFrames = 0; this->accumulatedTime = 0; this->lastticks = 0; this->timeElapsed = 0; } void CFramerateManager::init() { this->lastticks = SDL_GetTicks(); } void CFramerateManager::framerateDelay() { ui32 currentTicks = SDL_GetTicks(); timeElapsed = currentTicks - lastticks; // FPS is higher than it should be, then wait some time if (timeElapsed < rateticks) { SDL_Delay(ceil(this->rateticks) - timeElapsed); } accumulatedTime += timeElapsed; accumulatedFrames++; if(accumulatedFrames >= 100) { //about 2 second should be passed fps = ceil(1000.0 / (accumulatedTime/accumulatedFrames)); accumulatedTime = 0; accumulatedFrames = 0; }; currentTicks = SDL_GetTicks(); // recalculate timeElapsed for external calls via getElapsed() // limit it to 1000 ms to avoid breaking animation in case of huge lag (e.g. triggered breakpoint) timeElapsed = std::min(currentTicks - lastticks, 1000); lastticks = SDL_GetTicks(); }