diff --git a/glob2.vcxproj b/glob2.vcxproj index 44286e05..f8911fa3 100644 --- a/glob2.vcxproj +++ b/glob2.vcxproj @@ -262,6 +262,7 @@ + ZLIB_WINAPI;%(PreprocessorDefinitions) ZLIB_WINAPI;%(PreprocessorDefinitions) @@ -507,6 +508,7 @@ + @@ -622,4 +624,4 @@ - + \ No newline at end of file diff --git a/libgag/include/CursorManager.h b/libgag/include/CursorManager.h index 25f1cf4d..e0bd1827 100644 --- a/libgag/include/CursorManager.h +++ b/libgag/include/CursorManager.h @@ -67,6 +67,7 @@ namespace GAGCore CursorManager(); //! Load the cursor sprites void load(void); + void reinitTextures(void); //! Select the next type given the mouse position void nextTypeFromMouse(DrawableSurface *ds, int x, int y, bool button); //! Manually set the next type diff --git a/libgag/include/EventListener.h b/libgag/include/EventListener.h new file mode 100644 index 00000000..c08cadb9 --- /dev/null +++ b/libgag/include/EventListener.h @@ -0,0 +1,67 @@ +/* + Copyright (C) 2022 Nathan Mills + for any question or comment contact us at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ +#ifndef __EVENTLISTENER_H +#define __EVENTLISTENER_H +#include "GraphicContext.h" +#include +#include +#include +#include +#include +#include +#include + +#if defined(_WIN32) || defined(__MINGW32__) || defined(__MINGW64__) +#define WINDOWS_OR_MINGW 1 +#endif + +namespace GAGCore { +extern std::queue events; +class EventListener { +public: + EventListener(GraphicContext* gfx); + void run(); + void stop(); + bool isRunning(); + bool isResizing(); + int poll(SDL_Event* e); + static EventListener *instance(); + ~EventListener(); + static std::mutex startMutex; + static std::condition_variable startedCond; + static std::mutex doneMutex; + static std::condition_variable doneCond; + static std::recursive_mutex renderMutex; + void setPainter(std::function f); + void addPainter(const std::string& name, std::function f); + void removePainter(const std::string& name); + static void ensureContext(); + void paint(); +private: + std::function painter; + std::multimap > painters; + GraphicContext* gfx; + static EventListener* el; + std::atomic quit, done; + std::atomic depth; + + static std::mutex queueMutex; // used when pushing/popping queue. +}; +} +#endif //__EVENTLISTENER_H \ No newline at end of file diff --git a/libgag/include/SDLGraphicContext.h b/libgag/include/SDLGraphicContext.h index 24ee4f06..138700eb 100644 --- a/libgag/include/SDLGraphicContext.h +++ b/libgag/include/SDLGraphicContext.h @@ -156,6 +156,7 @@ namespace GAGCore protected: friend struct Color; friend class GraphicContext; + friend class Sprite; //! the underlying software SDL surface SDL_Surface *sdlsurface; //! The clipping rect, we do not draw outside it @@ -318,12 +319,16 @@ namespace GAGCore protected: //! the minimum acceptable resolution int minW, minH; + int prevW, prevH; SDL_Window *window = nullptr; + SDL_GLContext context = nullptr; friend class DrawableSurface; //! option flags Uint32 optionFlags; std::string windowTitle; std::string appIcon; + int resizeTimer; + Sint64 framesDrawn, frameStartResize, frameStopResize; public: //! Constructor. Create a new window of size (w,h). If useGPU is true, use GPU for accelerated 2D (OpenGL or DX) @@ -334,6 +339,14 @@ namespace GAGCore // modifiers virtual bool setRes(int w, int h, Uint32 flags); virtual void setRes(int w, int h) { setRes(w, h, optionFlags); } + virtual SDL_Surface *getOrCreateSurface(int w, int h, Uint32 flags); + virtual SDL_Rect getRes(); + virtual bool resChanged(); + virtual void createGLContext(); + virtual void unsetContext(); + static GraphicContext* instance(); + void resetMatrices(); + bool isResizing(); virtual void setClipRect(int x, int y, int w, int h); virtual void setClipRect(void); virtual void nextFrame(void); @@ -437,6 +450,8 @@ namespace GAGCore //! Load a sprite from the file, return true if any frame have been loaded bool load(const std::string filename); + + void reinit(); //! Set the (r,g,b) color to a sprite's base color virtual void setBaseColor(Uint8 r, Uint8 g, Uint8 b) { actColor = Color(r, g, b); } diff --git a/libgag/src/CursorManager.cpp b/libgag/src/CursorManager.cpp index cfe4f10b..ac670576 100644 --- a/libgag/src/CursorManager.cpp +++ b/libgag/src/CursorManager.cpp @@ -46,6 +46,14 @@ namespace GAGCore cursors.push_back(Toolkit::getSprite("data/gfx/cursor/mark")); setDefaultColor(); } + + void CursorManager::reinitTextures(void) + { + for (Sprite *cursor : cursors) + { + cursor->reinit(); + } + } void CursorManager::nextTypeFromMouse(DrawableSurface *ds, int x, int y, bool button) { diff --git a/libgag/src/EventListener.cpp b/libgag/src/EventListener.cpp new file mode 100644 index 00000000..35590dd0 --- /dev/null +++ b/libgag/src/EventListener.cpp @@ -0,0 +1,245 @@ +/* + Copyright (C) 2022 Nathan Mills + for any question or comment contact us at + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ +#include "EventListener.h" +#include +#include + +/* + The main thread opens the window and handles events; the logic thread handles + everything else. Every so often, the logic thread paints the screen. During + resize, the event watch function will call paint() on every registered painter. + + The logic thread is started in Glob2::run. +*/ +namespace GAGCore { +std::queue events = std::queue(); +std::mutex EventListener::queueMutex; +EventListener* EventListener::el = nullptr; +std::mutex EventListener::startMutex; +std::condition_variable EventListener::startedCond; +std::mutex EventListener::doneMutex; +std::condition_variable EventListener::doneCond; +std::recursive_mutex EventListener::renderMutex; + +#define SIZE_MOVE_TIMER_ID 1 + +// The depth variable is used to return early when indirect recursion happens +EventListener::EventListener(GraphicContext* gfx) +: painter(nullptr), depth(0) +{ + assert(gfx); + this->gfx = gfx; + el = this; + done = false; + quit = true; +} + +//! End the event listening loop +void EventListener::stop() +{ + quit = true; + std::unique_lock lock(doneMutex); + while (!done) { + doneCond.wait(lock); + } +} + +EventListener::~EventListener() +{ +} + +//! Create an OpenGL context or set existing context as current on this thread. +void EventListener::ensureContext() +{ + instance()->gfx->createGLContext(); +} + +//! deprecated; use addPainter/removePainter instead. +void EventListener::setPainter(std::function f) +{ + std::unique_lock lock(renderMutex); + painter = f; +} + +/** Add a painter to the list of painting functions to be called on window resize. + * name should be the class name that the function is called on + * f is the function to call to draw the screen. + */ +void EventListener::addPainter(const std::string& name, std::function f) +{ + std::unique_lock lock(renderMutex); + painters.insert(std::pair >(name, f)); +} + +/** Erase the latest painter added with the name `name` from the multimap + * Removes the most recently added painter with that name. + */ +void EventListener::removePainter(const std::string& name) +{ + if (painters.empty()) + assert("Tried to remove a painter when painters map is empty."); + std::unique_lock lock(renderMutex); + for (std::multimap >::reverse_iterator it = painters.rbegin(); it != painters.rend(); ++it) + { + if (it->first == name) + { + // There might be multiple Screens active, so we remove the one added last. + // For example, a Screen with an OverlayScreen above it. + painters.erase(--(it.base())); + break; + } + } +} + +//! Draw all the registered painters in order +void EventListener::paint() +{ + depth++; + if (depth > 1) + return; + if (painters.size()) + { + std::unique_lock lock(renderMutex); + gfx->createGLContext(); + for (std::multimap >::iterator it = painters.begin(); it != painters.end(); ++it) + { + it->second(); + } + gfx->nextFrame(); + } + depth--; +} + +//! Handle user resizing the game window on Microsoft Windows. +// TODO: Handle window resizing on macOS +//https://stackoverflow.com/a/51597338/8890345 +bool sizeMoveTimerRunning = false; +#ifdef WINDOWS_OR_MINGW +int eventWatch(void* self, SDL_Event* event) { + if (event->type == SDL_SYSWMEVENT) + { + const auto &winMessage = event->syswm.msg->msg.win; + if (winMessage.msg == WM_ENTERSIZEMOVE) + { + // the user started dragging, so create the timer (with the minimum timeout) + // if you have vsync enabled, then this shouldn't render unnecessarily + sizeMoveTimerRunning = SetTimer(GetActiveWindow(), SIZE_MOVE_TIMER_ID, USER_TIMER_MINIMUM, nullptr); + } + else if (winMessage.msg == WM_TIMER) + { + if (winMessage.wParam == SIZE_MOVE_TIMER_ID) + { + // call your render function + EventListener *el = reinterpret_cast(self); + if (el) + el->paint(); + } + } + } + return 0; +} +#endif + +bool EventListener::isResizing() +{ + return sizeMoveTimerRunning; +} +/** Listens for events and adds them to a queue. + * Call EventListener::poll to get an event from the queue. + */ +void EventListener::run() +{ + { + std::unique_lock lock(startMutex); + quit = false; + } + startedCond.notify_one(); +#ifdef WINDOWS_OR_MINGW + SDL_AddEventWatch(eventWatch, this); // register the event watch function + SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE); // we need the native Windows events, so we can listen to WM_ENTERSIZEMOVE and WM_TIMER +#endif + while (!quit) + { + SDL_Event event; + while (SDL_PollEvent(&event)) + { + if (event.type == SDL_QUIT) { + quit = true; + } + if (event.type == SDL_WINDOWEVENT && + (event.window.event == SDL_WINDOWEVENT_RESIZED || + event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED)) + { + /*glClearColor (0.0f, 0.0f, 0.0f, 1.0f ); + glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + SDL_GL_SwapWindow(_gc->window);*/ + /*if (painter) + { + painter(); + gfx->nextFrame(); + }*/ + } +#ifdef WINDOWS_OR_MINGW + if (sizeMoveTimerRunning) + { + // modal drag/size loop ended, so kill the timer + KillTimer(GetActiveWindow(), SIZE_MOVE_TIMER_ID); + sizeMoveTimerRunning = false; + } +#endif + { + std::lock_guard lock(queueMutex); + events.push(event); + } + } + } + { + std::unique_lock lock(doneMutex); + done = true; + } + doneCond.notify_one(); +} + +/** Drop-in replacement for SDL_PollEvent. + * Call this to get an event from the queue. + */ +int EventListener::poll(SDL_Event* e) +{ + std::lock_guard lock(queueMutex); + if (events.size()) { + *e = events.front(); + events.pop(); + return 1; + } + return 0; +} + +/** Gets the active EventListener instance. + * Does not initialize the EventListener if it is nullptr. + */ +EventListener *EventListener::instance() +{ + return el; +} +//! Checks if the EventListener is currently in the event handling loop. +bool EventListener::isRunning() +{ + return !quit; +} +} \ No newline at end of file diff --git a/libgag/src/GUIBase.cpp b/libgag/src/GUIBase.cpp index eee50771..6443d64f 100644 --- a/libgag/src/GUIBase.cpp +++ b/libgag/src/GUIBase.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include @@ -414,6 +415,7 @@ namespace GAGGUI Screen::~Screen() { + EventListener::instance()->removePainter("Screen"); for (std::set::iterator it=widgets.begin(); it!=widgets.end(); ++it) { delete (*it); @@ -426,6 +428,7 @@ namespace GAGGUI Sint32 frameWaitTime; this->gfx = gfx; + EventListener::instance()->addPainter("Screen", std::bind(&Screen::dispatchPaint, this)); // init widgets dispatchInit(); @@ -449,7 +452,8 @@ namespace GAGGUI SDL_Event lastMouseMotion, windowEvent, event; bool hadLastMouseMotion=false; bool wasWindowEvent=false; - while (SDL_PollEvent(&event)) + EventListener* el = EventListener::instance(); + while (el->poll(&event)) { switch (event.type) { @@ -470,12 +474,13 @@ namespace GAGGUI { windowEvent=event; wasWindowEvent=true; - } - break; - case SDL_WINDOWEVENT_RESIZED: - { - // FIXME: window resize is broken - // gfx->setRes(event.window.data1, event.window.data2); + if (event.window.event == SDL_WINDOWEVENT_RESIZED) + { + + // FIXME: window resize is broken + gfx->setRes(event.window.data1, event.window.data2); + onAction(NULL, SCREEN_RESIZED, gfx->getW(), gfx->getH()); + } } break; case SDL_WINDOWEVENT_SIZE_CHANGED: @@ -513,6 +518,12 @@ namespace GAGGUI break; } } + GraphicContext* gfx = GraphicContext::instance(); + if (gfx->resChanged()) { + SDL_Rect r = gfx->getRes(); + gfx->setRes(r.w, r.h); + onAction(NULL, SCREEN_RESIZED, gfx->getW(), gfx->getH()); + } if (hadLastMouseMotion) dispatchEvents(&lastMouseMotion); if (wasWindowEvent) @@ -666,6 +677,8 @@ namespace GAGGUI void Screen::dispatchPaint(void) { assert(gfx); + std::unique_lock lock(EventListener::renderMutex); + EventListener::ensureContext(); gfx->setClipRect(); paint(); for (std::set::iterator it=widgets.begin(); it!=widgets.end(); ++it) diff --git a/libgag/src/GUIMessageBox.cpp b/libgag/src/GUIMessageBox.cpp index 611e3769..745e204d 100644 --- a/libgag/src/GUIMessageBox.cpp +++ b/libgag/src/GUIMessageBox.cpp @@ -24,6 +24,7 @@ #include #include #include +#include using namespace GAGCore; @@ -110,7 +111,8 @@ namespace GAGGUI while(mbs->endValue<0) { Sint32 time = SDL_GetTicks(); - while (SDL_PollEvent(&event)) + EventListener* el = EventListener::instance(); + while (el->poll(&event)) { if (event.type==SDL_QUIT) break; diff --git a/libgag/src/GraphicContext.cpp b/libgag/src/GraphicContext.cpp index 1033c073..fc8ad794 100644 --- a/libgag/src/GraphicContext.cpp +++ b/libgag/src/GraphicContext.cpp @@ -21,6 +21,7 @@ #include #include #include +#include "EventListener.h" #include #include #include @@ -32,6 +33,7 @@ #include #include #include +#include #ifdef HAVE_CONFIG_H #include @@ -347,6 +349,8 @@ namespace GAGCore #ifdef HAVE_OPENGL if (_gc->optionFlags & GraphicContext::USEGPU) { + std::unique_lock lock(EventListener::renderMutex); + EventListener::ensureContext(); glState.setTexture(texture); void *pixelsPtr; @@ -1069,6 +1073,10 @@ namespace GAGCore void DrawableSurface::drawSurface(int x, int y, DrawableSurface *surface, int sx, int sy, int sw, int sh, Uint8 alpha) { + if (surface != _gc && (surface->dirty || _gc->isResizing())) + surface->uploadToTexture(); + if (this != _gc && (this->dirty || _gc->isResizing())) + this->uploadToTexture(); if (alpha == Color::ALPHA_OPAQUE) { #ifdef HAVE_OPENGL @@ -1671,7 +1679,7 @@ namespace GAGCore if (_gc->optionFlags & GraphicContext::USEGPU) { // upload - if (surface->dirty) + if (surface->dirty || isResizing()) surface->uploadToTexture(); // state change @@ -1919,6 +1927,7 @@ namespace GAGCore { minW = w; minH = h; + SDL_SetWindowMinimumSize(window, minW, minH); } VideoModes GraphicContext::listVideoModes() const @@ -1960,9 +1969,13 @@ namespace GAGCore return modes; } - GraphicContext::GraphicContext(int w, int h, Uint32 flags, const std::string title, const std::string icon): + GraphicContext::GraphicContext(int w, int h, Uint32 flags, const std::string title, const std::string icon) : windowTitle(title), - appIcon(icon) + appIcon(icon), + resizeTimer(0), + framesDrawn(0), + frameStartResize(-1), + frameStopResize(-1) { // some assert on the universe's structure assert(sizeof(Color) == 4); @@ -2006,8 +2019,85 @@ namespace GAGCore fprintf(stderr, "Toolkit : Graphic Context destroyed\n"); } + GraphicContext* GraphicContext::instance() + { + return _gc; + } + + std::mutex m; + void GraphicContext::createGLContext() + { + // enable GL context + if (optionFlags & USEGPU) + { + std::lock_guard l(m); + if (!context) + context = SDL_GL_CreateContext(window); + if (!context) + throw "no context"; + SDL_GL_MakeCurrent(window, context); + } + } + void GraphicContext::unsetContext() + { + if (optionFlags & USEGPU) + { + SDL_GL_MakeCurrent(window, nullptr); + } + } + bool GraphicContext::resChanged() + { + int w, h; + SDL_GetWindowSize(window, &w, &h); + return prevW != w || prevH != h; + } + SDL_Rect GraphicContext::getRes() + { + int w, h; + SDL_Rect r; + SDL_GetWindowSize(window, &w, &h); + r = {0, 0, w, h}; + return r; + } + void GraphicContext::resetMatrices() + { + // https://gamedev.stackexchange.com/questions/62691/opengl-resize-problem + int w = getW(), h = getH(); + glViewport(0, 0, w, h); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + } + + bool GraphicContext::isResizing() + { + static EventListener* instance = nullptr; + if (!instance) + instance = EventListener::instance(); + // Either currently resizing or is within 5 frames of stopping resize. + return instance->isResizing() || (frameStartResize <= frameStopResize && framesDrawn < (frameStopResize + 5)); + } + + SDL_Surface* GraphicContext::getOrCreateSurface(int w, int h, Uint32 flags) { + std::unique_lock lock(EventListener::renderMutex); + if (flags & USEGPU) + { + if (sdlsurface) + SDL_FreeSurface(sdlsurface); + // Can't use SDL_GetWindowSurface with OpenGL; the documentation forbids it. + sdlsurface = SDL_CreateRGBSurface(0, w, h, 32, 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000); + } + else + { + sdlsurface = SDL_GetWindowSurface(window); + } + return sdlsurface; + } + bool GraphicContext::setRes(int w, int h, Uint32 flags) { + static bool isLoading = true; // check dimension if (minW && (w < minW)) { @@ -2022,14 +2112,23 @@ namespace GAGCore h = minH; } + prevW = w; + prevH = h; + // set flags optionFlags = flags; Uint32 sdlFlags = 0; if (flags & FULLSCREEN) sdlFlags |= SDL_WINDOW_FULLSCREEN; // FIXME: window resize is broken - // if (flags & RESIZABLE) - // sdlFlags |= SDL_WINDOW_RESIZABLE; + if (flags & RESIZABLE && !isLoading) + { + sdlFlags |= SDL_WINDOW_RESIZABLE; + } + else + { + isLoading = false; + } #ifdef HAVE_OPENGL if (flags & USEGPU) { @@ -2041,14 +2140,34 @@ namespace GAGCore optionFlags &= ~USEGPU; #endif - // if window exists, delete it + // if window exists, resize it if (window) { - SDL_DestroyWindow(window); - window = nullptr; + SDL_SetWindowSize(window, w, h); + SDL_SetWindowResizable(window, SDL_TRUE); + getOrCreateSurface(w, h, flags); +#ifdef HAVE_OPENGL + if (flags & USEGPU) + { + resetMatrices(); + } +#endif + setClipRect(0, 0, w, h); + //nextFrame(); + } + else { + // create the new window and the surface + window = SDL_CreateWindow(windowTitle.c_str(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, w, h, sdlFlags); + sdlsurface = window != nullptr ? getOrCreateSurface(w, h, flags) : nullptr; +#ifdef HAVE_OPENGL + // enable GL context + if (flags & USEGPU) + { + if (!context) + createGLContext(); + resetMatrices(); + } +#endif } - // create the new window and the surface - window = SDL_CreateWindow(windowTitle.c_str(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, w, h, sdlFlags); - sdlsurface = window != nullptr ? SDL_GetWindowSurface(window) : nullptr; // check surface if (!sdlsurface) @@ -2060,12 +2179,6 @@ namespace GAGCore else { _gc = this; - // enable GL context - if (flags & USEGPU) - { - SDL_GLContext context = SDL_GL_CreateContext(window); - SDL_GL_MakeCurrent(window, context); - } // set _glFormat if ((optionFlags & USEGPU) && (_gc->sdlsurface->format->BitsPerPixel != 32)) { @@ -2163,6 +2276,10 @@ namespace GAGCore { int mx, my; unsigned b = SDL_GetMouseState(&mx, &my); + if (isResizing()) + { + cursorManager.reinitTextures(); + } cursorManager.nextTypeFromMouse(this, mx, my, b != 0); setClipRect(); cursorManager.draw(this, mx, my); @@ -2179,6 +2296,20 @@ namespace GAGCore { SDL_UpdateWindowSurface(window); } + bool isResizing = EventListener::instance()->isResizing(); + if (resizeTimer && !isResizing) + { + // Resize flag is set but we are not currently resizing. + frameStopResize = framesDrawn; + resizeTimer--; + } + if (!resizeTimer && isResizing) + { + // The user started resizing the window. + frameStartResize = framesDrawn; + resizeTimer++; + } + framesDrawn++; } } diff --git a/libgag/src/SConscript b/libgag/src/SConscript index 7e227754..4d6de955 100644 --- a/libgag/src/SConscript +++ b/libgag/src/SConscript @@ -7,7 +7,8 @@ GUITextArea.cpp GUIText.cpp GUITextInput.cpp GUIImage.cpp GUIProgressBar.cpp KeyPress.cpp Sprite.cpp StreamBackend.cpp Stream.cpp StreamFilter.cpp StringTable.cpp SupportFunctions.cpp TextStream.cpp Toolkit.cpp TrueTypeFont.cpp win32_dirent.cpp -GUITabScreen.cpp GUITabScreenWindow.cpp TextSort.cpp GUICheckList.cpp +GUITabScreen.cpp GUITabScreenWindow.cpp TextSort.cpp GUICheckList.cpp +EventListener.cpp """) libgag_just_server = Split(""" diff --git a/libgag/src/Sprite.cpp b/libgag/src/Sprite.cpp index 5293e3f3..d827e542 100644 --- a/libgag/src/Sprite.cpp +++ b/libgag/src/Sprite.cpp @@ -70,6 +70,28 @@ namespace GAGCore return getFrameCount() > 0; } + + void Sprite::reinit() + { + + for (DrawableSurface* image : images) + { + if (image) + image->uploadToTexture(); + } + for (RotatedImage* turned : rotated) + { + if (turned) + { + if (turned->orig) + turned->orig->uploadToTexture(); + for (auto& pair : turned->rotationMap) + { + pair.second->uploadToTexture(); + } + } + } + } DrawableSurface *Sprite::getRotatedSurface(int index) { diff --git a/libgag/src/Toolkit.cpp b/libgag/src/Toolkit.cpp index 6de1c2c7..fd42977d 100644 --- a/libgag/src/Toolkit.cpp +++ b/libgag/src/Toolkit.cpp @@ -23,6 +23,7 @@ #include #include #include "TrueTypeFont.h" +#include "EventListener.h" #ifndef YOG_SERVER_ONLY #include @@ -77,6 +78,13 @@ namespace GAGCore } #ifndef YOG_SERVER_ONLY + EventListener *el = EventListener::instance(); + if (el) + { + el->stop(); + delete el; + el = NULL; + } if (gc) { delete gc; diff --git a/src/EndGameScreen.cpp b/src/EndGameScreen.cpp index cbde4b3a..2b1aaff0 100644 --- a/src/EndGameScreen.cpp +++ b/src/EndGameScreen.cpp @@ -35,6 +35,7 @@ #include "GameGUILoadSave.h" #include "StreamBackend.h" #include "ReplayWriter.h" +#include "EventListener.h" EndGameStat::EndGameStat(int x, int y, int w, int h, Uint32 hAlign, Uint32 vAlign, Game *game) { @@ -567,7 +568,8 @@ void EndGameScreen::saveReplay(const char *dir, const char *ext) while(loadSaveScreen->endValue<0) { int time = SDL_GetTicks(); - while (SDL_PollEvent(&event)) + EventListener *el = EventListener::instance(); + while (el->poll(&event)) { loadSaveScreen->translateAndProcessEvent(&event); } diff --git a/src/Engine.cpp b/src/Engine.cpp index 6b83259b..896366f2 100644 --- a/src/Engine.cpp +++ b/src/Engine.cpp @@ -507,6 +507,14 @@ int Engine::run(void) if (nextGuiStep == 0) { // we draw + if (!gui.isRegistered) + { + std::unique_lock lock(EventListener::renderMutex); + EventListener::instance()->addPainter("GameGUI", std::bind(&GameGUI::drawAll, &gui, gui.localTeamNo)); + gui.isRegistered = true; + } + std::unique_lock lock(EventListener::renderMutex); + globalContainer->gfx->createGLContext(); gui.drawAll(gui.localTeamNo); globalContainer->gfx->nextFrame(); } diff --git a/src/FertilityCalculatorDialog.cpp b/src/FertilityCalculatorDialog.cpp index 085e4c15..934f705e 100644 --- a/src/FertilityCalculatorDialog.cpp +++ b/src/FertilityCalculatorDialog.cpp @@ -26,6 +26,7 @@ #include #include "StringTable.h" #include "Toolkit.h" +#include "EventListener.h" using namespace GAGCore; using namespace GAGGUI; @@ -66,7 +67,8 @@ void FertilityCalculatorDialog::execute() while(endValue<0) { Sint32 time = SDL_GetTicks(); - while (SDL_PollEvent(&event)) + EventListener *el = EventListener::instance(); + while (el->poll(&event)) { if (event.type==SDL_QUIT) break; diff --git a/src/GameGUI.cpp b/src/GameGUI.cpp index 55dfd7e5..0048855d 100644 --- a/src/GameGUI.cpp +++ b/src/GameGUI.cpp @@ -55,6 +55,8 @@ #include "ReplayWriter.h" #include "config.h" #include "Order.h" +#include "EventListener.h" +#include "SDLGraphicContext.h" #include @@ -182,13 +184,14 @@ GameGUI::GameGUI() 128, // width 128, //height Minimap::ShowFOW), // minimap mode - + isRegistered(false), ghostManager(game) { } GameGUI::~GameGUI() { + EventListener::instance()->removePainter("GameGUI"); for (ParticleSet::iterator it = particles.begin(); it != particles.end(); ++it) delete *it; } @@ -388,7 +391,8 @@ void GameGUI::step(void) bool wasWindowEvent=false; int oldMouseMapX = -1, oldMouseMapY = -1; // hopefully the values here will never matter // we get all pending events but for mousemotion we only keep the last one - while (SDL_PollEvent(&event)) + EventListener *el = EventListener::instance(); + while (el->poll(&event)) { if (event.type==SDL_MOUSEMOTION) { @@ -475,6 +479,11 @@ void GameGUI::step(void) processEvent(&event); } } + GraphicContext* gfx = GraphicContext::instance(); + if (gfx->resChanged()) { + SDL_Rect r = gfx->getRes(); + gfx->setRes(r.w, r.h); + } if (wasMouseMotion) processEvent(&mouseMotionEvent); if (wasWindowEvent) @@ -1117,6 +1126,20 @@ void GameGUI::processEvent(SDL_Event *event) else if (event->type==SDL_WINDOWEVENT) { handleActivation(event->window.data1, event->window.data2); + if (event->window.event == SDL_WINDOWEVENT_RESIZED || event->window.event == SDL_WINDOWEVENT_SIZE_CHANGED) + { + // FIXME: window resize is broken + int newW=event->window.data1; + int newH=event->window.data2; + newW&=(~(0x1F)); + newH&=(~(0x1F)); + if (newW<640) + newW=640; + if (newH<480) + newH=480; + printf("New size : %dx%d\n", newW, newH); + globalContainer->gfx->setRes(newW, newH); + } } else if (event->type==SDL_QUIT) { @@ -1124,20 +1147,6 @@ void GameGUI::processEvent(SDL_Event *event) orderQueue.push_back(shared_ptr(new PlayerQuitsGameOrder(localPlayer))); flushOutgoingAndExit=true; } - else if (event->type==SDL_WINDOWEVENT_RESIZED) - { - // FIXME: window resize is broken - /*int newW=event->window.data1; - int newH=event->window.data2; - newW&=(~(0x1F)); - newH&=(~(0x1F)); - if (newW<640) - newW=640; - if (newH<480) - newH=480; - printf("New size : %dx%d\n", newW, newH); - globalContainer->gfx->setRes(newW, newH);*/ - } } void GameGUI::handleActivation(Uint8 state, Uint8 gain) @@ -4376,6 +4385,8 @@ void GameGUI::drawInGameScrollableText(void) void GameGUI::drawAll(int team) { + std::unique_lock lock(EventListener::renderMutex); + EventListener::ensureContext(); // draw the map Uint32 drawOptions = (drawHealthFoodBar ? Game::DRAW_HEALTH_FOOD_BAR : 0) | (drawPathLines ? Game::DRAW_PATH_LINE : 0) | diff --git a/src/GameGUI.h b/src/GameGUI.h index b43db896..167d83e9 100644 --- a/src/GameGUI.h +++ b/src/GameGUI.h @@ -201,6 +201,10 @@ class GameGUI bool hardPause; bool isRunning; bool notmenu; + + // isRegistered tracks whether this instance of GameGUI has been registered with EventListener. + bool isRegistered; + //! true if user close the glob2 window. bool exitGlobCompletely; //! true if the game needs to flush all outgoing orders and exit diff --git a/src/Glob2.cpp b/src/Glob2.cpp index 3d982214..7b7ce197 100644 --- a/src/Glob2.cpp +++ b/src/Glob2.cpp @@ -20,9 +20,10 @@ #include "Glob2.h" #include "GlobalContainer.h" #include "YOGServer.h" +#include +#include #ifndef YOG_SERVER_ONLY - #include "CampaignEditor.h" #include "CampaignMenuScreen.h" #include "CampaignMainMenu.h" @@ -31,6 +32,7 @@ #include "CreditScreen.h" #include "EditorMainMenu.h" #include "Engine.h" +#include "EventListener.h" #include "Game.h" #include "GUIMessageBox.h" #include "Header.h" @@ -65,6 +67,11 @@ # include #endif +#if defined(__linux__) || defined(__APPLE__) +// for setting thread name +#include +#endif + #ifdef __APPLE__ # include # include @@ -211,14 +218,47 @@ int Glob2::runTestMapGeneration() } #endif // !YOG_SERVER_ONLY - +void Glob2::finish() +{ + // This is for the textshot code + GAGCore::DrawableSurface::printFinishingText(); + delete globalContainer; +} int Glob2::run(int argc, char *argv[]) { srand(time(NULL)); - - globalContainer=new GlobalContainer(); - globalContainer->parseArgs(argc, argv); - globalContainer->load(); + if (!globalContainer) { + globalContainer=new GlobalContainer(); + globalContainer->parseArgs(argc, argv); + } + if (!globalContainer->mainthrSet) { + globalContainer->mainthr = std::this_thread::get_id(); + globalContainer->mainthrSet = true; + if (!globalContainer->hostServer && !globalContainer->runNoX) { + globalContainer->logicThread = new std::thread(&Glob2::run, this, argc, argv); + } + } + if (!globalContainer->hostServer && + !globalContainer->runNoX && + std::this_thread::get_id() == globalContainer->mainthr) { + // Handle events in main thread + globalContainer->load(true); + finish(); + return 0; + } + else { + // set thread name + const char* name = "Game logic"; +#ifdef WINDOWS_OR_MINGW + std::vector wideName(4096); + MultiByteToWideChar(CP_ACP, 0, name, -1, wideName.data(), 4096); + SetThreadDescription(GetCurrentThread(), wideName.data()); +#elif defined(__linux__) || defined(__APPLE__) + pthread_setname_np(pthread_self(), name); +#endif + // Handle game logic in logic thread + globalContainer->load(false); + } if ( SDLNet_Init() < 0 ) { @@ -407,9 +447,20 @@ int Glob2::run(int argc, char *argv[]) } } - // This is for the textshot code - GAGCore::DrawableSurface::printFinishingText(); - delete globalContainer; + // quit event loop so logic thread can be joined by main thread + if (globalContainer->logicThread) + { + EventListener *el = EventListener::instance(); + el->stop(); + } + + // Deleting globalContainer indirectly deletes GraphicContext whose + // destructor calls SDL_Quit(). I think SDL_Quit needs to be called from + // same thread that called SDL_Init. + if (std::this_thread::get_id() == globalContainer->mainthr) + { + finish(); + } #endif // !YOG_SERVER_ONLY diff --git a/src/Glob2.h b/src/Glob2.h index 44feec14..8d7c1760 100644 --- a/src/Glob2.h +++ b/src/Glob2.h @@ -37,6 +37,7 @@ class Glob2 ///Generates random maps non stop until the game crashes int runTestMapGeneration(); int run(int argc, char *argv[]); + void finish(); }; #endif diff --git a/src/GlobalContainer.cpp b/src/GlobalContainer.cpp index 315483ff..a1192371 100644 --- a/src/GlobalContainer.cpp +++ b/src/GlobalContainer.cpp @@ -16,15 +16,17 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ - +#include #include #include #include +#include "EventListener.h" #include "FileManager.h" #include "GameGUIKeyActions.h" #include "Glob2Screen.h" #include "Glob2Style.h" +#include "Glob2.h" #include "GlobalContainer.h" #include "Header.h" #include "IntBuildingType.h" @@ -127,6 +129,9 @@ GlobalContainer::GlobalContainer(void) replayShowAreas = false; replayShowFlags = true; + mainthrSet = false; + logicThread = nullptr; + #ifndef YOG_SERVER_ONLY replayReader = NULL; replayWriter = NULL; @@ -504,16 +509,35 @@ void GlobalContainer::updateLoadProgressScreen(int value) gfx->nextFrame(); } +EventListener* el = NULL; // glob2-client specific actions here. -void GlobalContainer::loadClient(void) +void GlobalContainer::loadClient(bool runEventListener) { if (!runNoX) { - // create graphic context - gfx = Toolkit::initGraphic(settings.screenWidth, settings.screenHeight, settings.screenFlags, "Globulation 2", "glob 2"); - gfx->setMinRes(640, 480); - //gfx->setQuality((settings.optionFlags & OPTION_LOW_SPEED_GFX) != 0 ? GraphicContext::LOW_QUALITY : GraphicContext::HIGH_QUALITY); + if (runEventListener) { + // create graphic context + gfx = Toolkit::initGraphic(settings.screenWidth, settings.screenHeight, settings.screenFlags, "Globulation 2", "glob 2"); + gfx->setMinRes(640, 480); + //gfx->setQuality((settings.optionFlags & OPTION_LOW_SPEED_GFX) != 0 ? GraphicContext::LOW_QUALITY : GraphicContext::HIGH_QUALITY); + + gfx->unsetContext(); + el = new EventListener(gfx); + el->run(); + logicThread->join(); + delete logicThread; + return; + } + { + std::unique_lock lock(EventListener::startMutex); + while (!el || !el->isRunning()) { + EventListener::startedCond.wait(lock); + } + } + gfx->createGLContext(); + // Next line fixes white screen during loading screen in software rendered mode. + gfx->getOrCreateSurface(gfx->getW(), gfx->getH(), gfx->getOptionFlags()); // load data required for drawing progress screen title = new DrawableSurface("data/gfx/title.png"); terrain = Toolkit::getSprite("data/gfx/terrain"); @@ -616,35 +640,38 @@ void GlobalContainer::loadClient(void) Style::style = new Glob2Style; updateLoadProgressScreen(100); + gfx->setRes(gfx->getW(), gfx->getH()); } } #endif // !YOG_SERVER_ONLY -void GlobalContainer::load(void) +void GlobalContainer::load(bool runEventListener) { - // load texts - if (!Toolkit::getStringTable()->load("data/texts.list.txt")) - { - std::cerr << "Fatal error : while loading \"data/texts.list.txt\"" << std::endl; - assert(false); - exit(-1); - } - // load texts - if (!Toolkit::getStringTable()->loadIncompleteList("data/texts.incomplete.txt")) - { - std::cerr << "Fatal error : while loading \"data/texts.incomplete.txt\"" << std::endl; - assert(false); - exit(-1); + if (runEventListener) { + // load texts + if (!Toolkit::getStringTable()->load("data/texts.list.txt")) + { + std::cerr << "Fatal error : while loading \"data/texts.list.txt\"" << std::endl; + assert(false); + exit(-1); + } + // load texts + if (!Toolkit::getStringTable()->loadIncompleteList("data/texts.incomplete.txt")) + { + std::cerr << "Fatal error : while loading \"data/texts.incomplete.txt\"" << std::endl; + assert(false); + exit(-1); + } + + Toolkit::getStringTable()->setLang(Toolkit::getStringTable()->getLangCode(settings.language)); + // load default unit types + Race::loadDefault(); + // load resources types + ressourcesTypes.load("data/ressources.txt"); ///TODO: coding in english or french? english is resources, french is ressources } - - Toolkit::getStringTable()->setLang(Toolkit::getStringTable()->getLangCode(settings.language)); - // load default unit types - Race::loadDefault(); - // load resources types - ressourcesTypes.load("data/ressources.txt"); ///TODO: coding in english or french? english is resources, french is ressources #ifndef YOG_SERVER_ONLY - loadClient(); + loadClient(runEventListener); #endif // !YOG_SERVER_ONLY } diff --git a/src/GlobalContainer.h b/src/GlobalContainer.h index cd0ed9de..8724bdcc 100644 --- a/src/GlobalContainer.h +++ b/src/GlobalContainer.h @@ -23,6 +23,9 @@ #include "BuildingsTypes.h" #include "RessourcesTypes.h" #include "Settings.h" +#include "EventListener.h" +#include +#include namespace GAGCore { @@ -41,6 +44,7 @@ class UnitsSkins; class ReplayReader; class ReplayWriter; +extern EventListener* el; class GlobalContainer { public: @@ -59,9 +63,9 @@ class GlobalContainer void parseArgs(int argc, char *argv[]); #ifndef YOG_SERVER_ONLY - void loadClient(void); + void loadClient(bool runEventListener); #endif // !YOG_SERVER_ONLY - void load(void); + void load(bool runEventListener); //void setUsername(const std::string &name); //const std::string &getUsername(void) { return settings.getUsername(); } @@ -138,6 +142,12 @@ class GlobalContainer bool replayShowAreas; //!< Show areas of gui.localPlayer or not. Can be edited real-time. bool replayShowFlags; //!< Show all flags or show none. Can be edited real-time. + // logic thread handles events in the queue, game logic, rendering, etc. + std::thread* logicThread; + // main thread listens for SDL events and adds them to a queue + std::thread::id mainthr; + std::atomic mainthrSet; + #ifndef YOG_SERVER_ONLY ReplayReader *replayReader; //!< Reads and processes replay files, and outputs orders ReplayWriter *replayWriter; //!< Writes orders into replay files diff --git a/src/MapEdit.cpp b/src/MapEdit.cpp index 172bf1e8..3566c237 100644 --- a/src/MapEdit.cpp +++ b/src/MapEdit.cpp @@ -37,6 +37,8 @@ #include "Utilities.h" #include "FertilityCalculatorDialog.h" #include "GUIMessageBox.h" +#include "EventListener.h" +#include "SDLGraphicContext.h" #define RIGHT_MENU_WIDTH 160 @@ -58,6 +60,13 @@ void MapEditorWidget::drawSelf() } +// If the window gets wider, we need to move the widgets further to the right. +// Likewize, if the window becomes smaller, the widgets should move to the left. +int MapEditorWidget::adjustX() +{ + return globalContainer->gfx->getW()-area.initialWindowWidth; +} + void MapEditorWidget::disable() { @@ -99,7 +108,7 @@ void BuildingSelectorWidget::draw() int imgid = bt->miniSpriteImage; int x, y; - x=area.x; + x=area.x+adjustX(); y=area.y; Sprite *buildingSprite; @@ -140,7 +149,7 @@ void TeamColorSelector::draw() { for(int n=0; n<16; ++n) { - const int xpos = area.x + (n%6)*16; + const int xpos = area.x + adjustX() + (n%6)*16; const int ypos = area.y + (n/6)*16; if(me.game.teams[n]) { @@ -165,7 +174,7 @@ SingleLevelSelector::SingleLevelSelector(MapEdit& me, const widgetRectangle& are void SingleLevelSelector::draw() { - globalContainer->gfx->drawSprite(area.x, area.y, me.menu, 30+level-1, (level-1)==levelNum ? 128 : 255); + globalContainer->gfx->drawSprite(area.x+adjustX(), area.y, me.menu, 30+level-1, (level-1)==levelNum ? 128 : 255); } @@ -182,9 +191,9 @@ void PanelIcon::draw() { // draw buttons if (me.panelMode==panelModeHilight) - globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->gamegui, iconNumber+1); + globalContainer->gfx->drawSprite(area.x+adjustX(), area.y, globalContainer->gamegui, iconNumber+1); else - globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->gamegui, iconNumber); + globalContainer->gfx->drawSprite(area.x+adjustX(), area.y, globalContainer->gamegui, iconNumber); } @@ -202,9 +211,9 @@ void MenuIcon::draw() { // draw buttons if (me.showingMenuScreen) - globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->gamegui, 7); + globalContainer->gfx->drawSprite(area.x+adjustX(), area.y, globalContainer->gamegui, 7); else - globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->gamegui, 6); + globalContainer->gfx->drawSprite(area.x+adjustX(), area.y, globalContainer->gamegui, 6); } @@ -223,25 +232,25 @@ void ZoneSelector::draw() bool isSelected=false; if(zoneType==ForbiddenZone) { - globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->gamegui, 13); + globalContainer->gfx->drawSprite(area.x+adjustX(), area.y, globalContainer->gamegui, 13); if(me.brushType==MapEdit::ForbiddenBrush) isSelected=true; } else if(zoneType==GuardingZone) { - globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->gamegui, 14); + globalContainer->gfx->drawSprite(area.x+adjustX(), area.y, globalContainer->gamegui, 14); if(me.brushType==MapEdit::GuardAreaBrush) isSelected=true; } else if(zoneType==ClearingZone) { - globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->gamegui, 25); + globalContainer->gfx->drawSprite(area.x+adjustX(), area.y, globalContainer->gamegui, 25); if(me.brushType==MapEdit::ClearAreaBrush) isSelected=true; } if(me.selectionMode==MapEdit::PlaceZone && isSelected) { - globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->gamegui, 22); + globalContainer->gfx->drawSprite(area.x+adjustX(), area.y, globalContainer->gamegui, 22); } } @@ -257,7 +266,7 @@ BrushSelector::BrushSelector(MapEdit& me, const widgetRectangle& area, const std void BrushSelector::draw() { - brushTool.draw(area.x, area.y); + brushTool.draw(area.x+adjustX(), area.y); } @@ -280,23 +289,23 @@ void UnitSelector::draw() { if(me.selectionMode==MapEdit::PlaceUnit && me.placingUnit==MapEdit::Worker) drawSelection=true; - globalContainer->gfx->drawSprite(area.x, area.y, unitSprite, 64); + globalContainer->gfx->drawSprite(area.x+adjustX(), area.y, unitSprite, 64); } else if(unitType==EXPLORER) { if(me.selectionMode==MapEdit::PlaceUnit && me.placingUnit==MapEdit::Explorer) drawSelection=true; - globalContainer->gfx->drawSprite(area.x, area.y, unitSprite, 0); + globalContainer->gfx->drawSprite(area.x+adjustX(), area.y, unitSprite, 0); } else if(unitType==WARRIOR) { if(me.selectionMode==MapEdit::PlaceUnit && me.placingUnit==MapEdit::Warrior) drawSelection=true; - globalContainer->gfx->drawSprite(area.x, area.y, unitSprite, 256); + globalContainer->gfx->drawSprite(area.x+adjustX(), area.y, unitSprite, 256); } if(drawSelection) { - globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->gamegui, 23); + globalContainer->gfx->drawSprite(area.x+adjustX(), area.y, globalContainer->gamegui, 23); } } @@ -313,29 +322,29 @@ TerrainSelector::TerrainSelector(MapEdit& me, const widgetRectangle& area, const void TerrainSelector::draw() { if(terrainType==Grass) - globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->terrain, 0); + globalContainer->gfx->drawSprite(area.x+adjustX(), area.y, globalContainer->terrain, 0); if(terrainType==Sand) - globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->terrain, 128); + globalContainer->gfx->drawSprite(area.x+adjustX(), area.y, globalContainer->terrain, 128); if(terrainType==Water) - globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->terrain, 259); + globalContainer->gfx->drawSprite(area.x+adjustX(), area.y, globalContainer->terrain, 259); if(terrainType==Wheat) - globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->ressources, 19); + globalContainer->gfx->drawSprite(area.x+adjustX(), area.y, globalContainer->ressources, 19); if(terrainType==Trees) - globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->ressources, 2); + globalContainer->gfx->drawSprite(area.x+adjustX(), area.y, globalContainer->ressources, 2); if(terrainType==Stone) - globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->ressources, 34); + globalContainer->gfx->drawSprite(area.x+adjustX(), area.y, globalContainer->ressources, 34); if(terrainType==Algae) - globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->ressources, 44); + globalContainer->gfx->drawSprite(area.x+adjustX(), area.y, globalContainer->ressources, 44); if(terrainType==Papyrus) - globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->ressources, 24); + globalContainer->gfx->drawSprite(area.x+adjustX(), area.y, globalContainer->ressources, 24); if(terrainType==CherryTree) - globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->ressources, 54); + globalContainer->gfx->drawSprite(area.x+adjustX(), area.y, globalContainer->ressources, 54); if(terrainType==OrangeTree) - globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->ressources, 59); + globalContainer->gfx->drawSprite(area.x+adjustX(), area.y, globalContainer->ressources, 59); if(terrainType==PruneTree) - globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->ressources, 64); + globalContainer->gfx->drawSprite(area.x+adjustX(), area.y, globalContainer->ressources, 64); if(me.terrainType==terrainType) - globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->gamegui, 22); + globalContainer->gfx->drawSprite(area.x+adjustX(), area.y, globalContainer->gamegui, 22); } @@ -350,15 +359,15 @@ BlueButton::BlueButton(MapEdit& me, const widgetRectangle& area, const std::stri void BlueButton::draw() { - globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->gamegui, 12); + globalContainer->gfx->drawSprite(area.x+adjustX(), area.y, globalContainer->gamegui, 12); if(selected) - globalContainer->gfx->drawFilledRect(area.x+9, area.y+3, 94, 10, 128, 128, 192); + globalContainer->gfx->drawFilledRect(area.x+9+adjustX(), area.y+3, 94, 10, 128, 128, 192); std::string translatedText; translatedText=Toolkit::getStringTable()->getString(text.c_str()); int len=globalContainer->littleFont->getStringWidth(translatedText.c_str()); int h=globalContainer->littleFont->getStringHeight(translatedText.c_str()); - globalContainer->gfx->drawString(area.x+9+((94-len)/2), area.y+((16-h)/2), globalContainer->littleFont, translatedText); + globalContainer->gfx->drawString(area.x+adjustX()+9+((94-len)/2), area.y+((16-h)/2), globalContainer->littleFont, translatedText); } @@ -386,10 +395,10 @@ PlusIcon::PlusIcon(MapEdit& me, const widgetRectangle& area, const std::string& void PlusIcon::draw() { - globalContainer->gfx->drawFilledRect(area.x, area.y, 32, 32, Color(75,0,200)); - globalContainer->gfx->drawRect(area.x, area.y, 32, 32, Color::white); - globalContainer->gfx->drawFilledRect(area.x + 15, area.y + 6, 2, 20, Color::white); - globalContainer->gfx->drawFilledRect(area.x + 6, area.y + 15, 20, 2, Color::white); + globalContainer->gfx->drawFilledRect(area.x+adjustX(), area.y, 32, 32, Color(75,0,200)); + globalContainer->gfx->drawRect(area.x+adjustX(), area.y, 32, 32, Color::white); + globalContainer->gfx->drawFilledRect(area.x+adjustX() + 15, area.y + 6, 2, 20, Color::white); + globalContainer->gfx->drawFilledRect(area.x+adjustX() + 6, area.y + 15, 20, 2, Color::white); } @@ -404,9 +413,9 @@ MinusIcon::MinusIcon(MapEdit& me, const widgetRectangle& area, const std::string void MinusIcon::draw() { - globalContainer->gfx->drawFilledRect(area.x, area.y, 32, 32, Color(75,0,200)); - globalContainer->gfx->drawRect(area.x, area.y, 32, 32, Color::white); - globalContainer->gfx->drawFilledRect(area.x + 6, area.y + 15, 20, 2, Color::white); + globalContainer->gfx->drawFilledRect(area.x+adjustX(), area.y, 32, 32, Color(75,0,200)); + globalContainer->gfx->drawRect(area.x+adjustX(), area.y, 32, 32, Color::white); + globalContainer->gfx->drawFilledRect(area.x+adjustX() + 6, area.y + 15, 20, 2, Color::white); } @@ -421,7 +430,7 @@ UnitInfoTitle::UnitInfoTitle(MapEdit& me, const widgetRectangle& area, const std void UnitInfoTitle::draw() { - const int xpos=area.x; + const int xpos=area.x+adjustX(); const int ypos=area.y; Unit* u=unit; @@ -467,7 +476,7 @@ UnitPicture::UnitPicture(MapEdit& me, const widgetRectangle& area, const std::st void UnitPicture::draw() { - const int xpos=area.x; + const int xpos=area.x+adjustX(); const int ypos=area.y; // draw unit's image @@ -536,7 +545,7 @@ FractionValueText::~FractionValueText() void FractionValueText::draw() { - globalContainer->gfx->drawString(area.x, area.y, globalContainer->littleFont, FormatableString("%0: %1/%2").arg(Toolkit::getStringTable()->getString(label.c_str())).arg(*numerator).arg(*denominator).c_str()); + globalContainer->gfx->drawString(area.x+adjustX(), area.y, globalContainer->littleFont, FormatableString("%0: %1/%2").arg(Toolkit::getStringTable()->getString(label.c_str())).arg(*numerator).arg(*denominator).c_str()); } @@ -585,11 +594,11 @@ void ValueScrollBox::draw() //Sometimes a scrollbox gets initiated with max-value 0. A turret construction site has 0/0 stone and 0/0 shots. To not run into arithmetic exceptions those cases are treated here. if((*max) != 0) { - globalContainer->gfx->setClipRect(area.x, area.y, 112, 16); - globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->gamegui, 9); + globalContainer->gfx->setClipRect(area.x+adjustX(), area.y, 112, 16); + globalContainer->gfx->drawSprite(area.x+adjustX(), area.y, globalContainer->gamegui, 9); int size=((*value)*92)/(*max); - globalContainer->gfx->setClipRect(area.x+10, area.y, size, 16); - globalContainer->gfx->drawSprite(area.x+10, area.y+3, globalContainer->gamegui, 10); + globalContainer->gfx->setClipRect(area.x+10+adjustX(), area.y, size, 16); + globalContainer->gfx->drawSprite(area.x+10+adjustX(), area.y+3, globalContainer->gamegui, 10); globalContainer->gfx->setClipRect(); } } @@ -657,7 +666,7 @@ void BuildingInfoTitle::draw() globalContainer->littleFont->pushStyle(Font::Style(Font::STYLE_NORMAL, r, g, b)); int titleLen = globalContainer->littleFont->getStringWidth(title.c_str()); - int titlePos = area.x+((area.width-titleLen)/2); + int titlePos = area.x+adjustX()+((area.width-titleLen)/2); globalContainer->gfx->drawString(titlePos, area.y, globalContainer->littleFont, title.c_str()); globalContainer->littleFont->popStyle(); } @@ -700,8 +709,8 @@ void BuildingPicture::draw() int dx = (56-miniSprite->getW(imgid))/2; int dy = (46-miniSprite->getH(imgid))/2; miniSprite->setBaseColor(selBuild->owner->color); - globalContainer->gfx->drawSprite(area.x+dx, area.y+dy, miniSprite, imgid); - globalContainer->gfx->drawSprite(area.x, area.y, globalContainer->gamegui, 18); + globalContainer->gfx->drawSprite(area.x+dx+adjustX(), area.y+dy, miniSprite, imgid); + globalContainer->gfx->drawSprite(area.x+adjustX(), area.y, globalContainer->gamegui, 18); } @@ -729,9 +738,9 @@ void TextLabel::draw() int titleWidth = globalContainer->littleFont->getStringWidth(label.c_str()); int titleHeight = globalContainer->littleFont->getStringHeight(label.c_str()); if(centered) - globalContainer->gfx->drawString(area.x+(area.width-titleWidth)/2, area.y+(area.height-titleHeight)/2, globalContainer->littleFont, label.c_str()); + globalContainer->gfx->drawString(area.x+adjustX()+(area.width-titleWidth)/2, area.y+(area.height-titleHeight)/2, globalContainer->littleFont, label.c_str()); else - globalContainer->gfx->drawString(area.x, area.y, globalContainer->littleFont, label.c_str()); + globalContainer->gfx->drawString(area.x+adjustX(), area.y, globalContainer->littleFont, label.c_str()); } @@ -755,7 +764,7 @@ void NumberCycler::draw() { std::stringstream s; s<gfx->drawString(area.x, area.y, globalContainer->standardFont, s.str().c_str()); + globalContainer->gfx->drawString(area.x+adjustX(), area.y, globalContainer->standardFont, s.str().c_str()); } @@ -788,17 +797,17 @@ Checkbox::Checkbox(MapEdit& me, const widgetRectangle& area, const std::string& void Checkbox::draw() { - globalContainer->gfx->drawRect(area.x, area.y, 16, 16, Color::white); + globalContainer->gfx->drawRect(area.x+adjustX(), area.y, 16, 16, Color::white); if(isActivated) { - globalContainer->gfx->drawLine(area.x+4, area.y+4, area.x+12, area.y+12, Color::white); - globalContainer->gfx->drawLine(area.x+12, area.y+4, area.x+4, area.y+12, Color::white); + globalContainer->gfx->drawLine(area.x+4+adjustX(), area.y+4, area.x+adjustX()+12, area.y+12, Color::white); + globalContainer->gfx->drawLine(area.x+12+adjustX(), area.y+4, area.x+adjustX()+4, area.y+12, Color::white); } std::string translatedText; translatedText=Toolkit::getStringTable()->getString(text.c_str()); - globalContainer->gfx->drawString(area.x+20, area.y, globalContainer->littleFont, translatedText); + globalContainer->gfx->drawString(area.x+20+adjustX(), area.y, globalContainer->littleFont, translatedText); } @@ -1107,6 +1116,7 @@ MapEdit::MapEdit() MapEdit::~MapEdit() { + EventListener::instance()->removePainter("MapEdit"); Toolkit::releaseSprite("data/gui/editor"); for(std::vector::iterator i=mew.begin(); i!=mew.end(); ++i) { @@ -1198,12 +1208,59 @@ bool MapEdit::save(const std::string filename, const std::string name) } } +void MapEdit::draw(void) +{ + std::unique_lock lock(EventListener::renderMutex); + EventListener::ensureContext(); + drawMap(0, 0, globalContainer->gfx->getW() - 0, globalContainer->gfx->getH(), true, true); + + drawMenu(); + drawMiniMap(); + wasMinimapRendered = false; + drawWidgets(); + if (showingMenuScreen) + { + globalContainer->gfx->setClipRect(); + menuScreen->dispatchTimer(startTick); + menuScreen->dispatchPaint(); + globalContainer->gfx->drawSurface((int)menuScreen->decX, (int)menuScreen->decY, menuScreen->getSurface()); + } + if (showingLoad || showingSave) + { + globalContainer->gfx->setClipRect(); + loadSaveScreen->dispatchTimer(startTick); + loadSaveScreen->dispatchPaint(); + globalContainer->gfx->drawSurface((int)loadSaveScreen->decX, (int)loadSaveScreen->decY, loadSaveScreen->getSurface()); + } + if (showingScriptEditor) + { + globalContainer->gfx->setClipRect(); + scriptEditor->dispatchTimer(startTick); + scriptEditor->dispatchPaint(); + globalContainer->gfx->drawSurface((int)scriptEditor->decX, (int)scriptEditor->decY, scriptEditor->getSurface()); + } + if (showingTeamsEditor) + { + globalContainer->gfx->setClipRect(); + teamsEditor->dispatchTimer(startTick); + teamsEditor->dispatchPaint(); + globalContainer->gfx->drawSurface((int)teamsEditor->decX, (int)teamsEditor->decY, teamsEditor->getSurface()); + } + if (isShowingAreaName) + { + globalContainer->gfx->setClipRect(); + areaName->dispatchTimer(startTick); + areaName->dispatchPaint(); + globalContainer->gfx->drawSurface((int)areaName->decX, (int)areaName->decY, areaName->getSurface()); + } +} int MapEdit::run(int sizeX, int sizeY, TerrainType terrainType) { game.map.setSize(sizeX, sizeY, terrainType); game.map.setGame(&game); + EventListener::instance()->addPainter("MapEdit", std::bind(&MapEdit::draw, this)); return run(); } @@ -1228,7 +1285,6 @@ int MapEdit::run(void) bool isRunning=true; int returnCode=0; - Uint32 startTick, endTick, deltaTick; while (isRunning) { //SDL_Event event; @@ -1236,11 +1292,18 @@ int MapEdit::run(void) // we get all pending events but for mousemotion we only keep the last one SDL_Event event; - while (SDL_PollEvent(&event)) + EventListener *el = EventListener::instance(); + while (el->poll(&event)) { processEvent(event); } + GraphicContext *gfx = GraphicContext::instance(); + if (gfx->resChanged()) { + SDL_Rect r = gfx->getRes(); + gfx->setRes(r.w, r.h); + } + // While processing events the user could've tried to load a map that failed. // Then we can't go through drawing everything because that would segfault. if(doQuitAfterLoadSave && !showingSave) @@ -1273,49 +1336,8 @@ int MapEdit::run(void) performAction("no ressource growth area drag motion"); } - drawMap(0, 0, globalContainer->gfx->getW()-0, globalContainer->gfx->getH(), true, true); - - drawMenu(); - drawMiniMap(); - wasMinimapRendered=false; - drawWidgets(); - if(showingMenuScreen) - { - globalContainer->gfx->setClipRect(); - menuScreen->dispatchTimer(startTick); - menuScreen->dispatchPaint(); - globalContainer->gfx->drawSurface((int)menuScreen->decX, (int)menuScreen->decY, menuScreen->getSurface()); - } - if(showingLoad || showingSave) - { - globalContainer->gfx->setClipRect(); - loadSaveScreen->dispatchTimer(startTick); - loadSaveScreen->dispatchPaint(); - globalContainer->gfx->drawSurface((int)loadSaveScreen->decX, (int)loadSaveScreen->decY, loadSaveScreen->getSurface()); - } - if(showingScriptEditor) - { - globalContainer->gfx->setClipRect(); - scriptEditor->dispatchTimer(startTick); - scriptEditor->dispatchPaint(); - globalContainer->gfx->drawSurface((int)scriptEditor->decX, (int)scriptEditor->decY, scriptEditor->getSurface()); - } - if(showingTeamsEditor) - { - globalContainer->gfx->setClipRect(); - teamsEditor->dispatchTimer(startTick); - teamsEditor->dispatchPaint(); - globalContainer->gfx->drawSurface((int)teamsEditor->decX, (int)teamsEditor->decY, teamsEditor->getSurface()); - } - if(isShowingAreaName) - { - globalContainer->gfx->setClipRect(); - areaName->dispatchTimer(startTick); - areaName->dispatchPaint(); - globalContainer->gfx->drawSurface((int)areaName->decX, (int)areaName->decY, areaName->getSurface()); - } - - + draw(); + globalContainer->gfx->nextFrame(); @@ -3195,7 +3217,8 @@ bool MapEdit::findAction(int x, int y) MapEditorWidget* mi=*i; if(mi->is_in(x, y) && mi->enabled) { - mi->handleClick(mouseX-mi->area.x, mouseY-mi->area.y); + int adjustX = globalContainer->gfx->getW() - mi->area.initialWindowWidth; + mi->handleClick(mouseX-(mi->area.x+adjustX), mouseY-mi->area.y); return true; } } diff --git a/src/MapEdit.h b/src/MapEdit.h index de444303..1b9c892b 100644 --- a/src/MapEdit.h +++ b/src/MapEdit.h @@ -38,14 +38,18 @@ ///A generic rectangle structure used for a variety of purposes, but mainly for the convience of the widget system struct widgetRectangle { - widgetRectangle(int x, int y, int width, int height) : x(x), y(y), width(width), height(height) {} - widgetRectangle() : x(0), y(0), width(0), height(0) {} - bool is_in(int posx, int posy) { return posx>x && posx<(x+width) && posy>y && posy<(y+height); } + widgetRectangle(int x, int y, int width, int height) : x(x), y(y), width(width), height(height), initialWindowWidth(globalContainer->gfx->getW()) {} + widgetRectangle() : x(0), y(0), width(0), height(0), initialWindowWidth(0) {} + bool is_in(int posx, int posy) { return posx>(x+globalContainer->gfx->getW()-initialWindowWidth) && + posx<(x+globalContainer->gfx->getW()-initialWindowWidth+width) && + posy>y && + posy<(y+height); } int x; int y; int width; int height; + int initialWindowWidth; }; class MapEdit; @@ -78,6 +82,7 @@ class MapEditorWidget ///This function must be implemented by all derived classes. This is where the widget draws itself. It should use area.x ///and area.y to get the cordinates. virtual void draw()=0; + int adjustX(); friend class MapEdit; protected: MapEdit& me; @@ -396,6 +401,8 @@ class MapEdit ///Saves the game to a particular file name bool save(const std::string filename, const std::string name); + void draw(void); + ///Updates the editor after map generation void update(); @@ -773,6 +780,7 @@ class MapEdit ///Handles a click or drag of the no ressource growth area placement tool void handleNoRessourceGrowthClick(int mx, int my); + Uint32 startTick, endTick, deltaTick; }; diff --git a/src/Minimap.cpp b/src/Minimap.cpp index e6416fe7..c379efee 100644 --- a/src/Minimap.cpp +++ b/src/Minimap.cpp @@ -71,6 +71,9 @@ void Minimap::draw(int localteam, int viewportX, int viewportY, int viewportW, i { if (noX) return; + // Keep the minimap in the right place even if window is resized. + gameWidth = globalContainer->gfx->getW(); + // Compute the position of the minimap if it needs to be scaled & centered computeMinimapPositioning(); diff --git a/src/ScriptEditorScreen.cpp b/src/ScriptEditorScreen.cpp index 002cc9c3..23b9b6f4 100644 --- a/src/ScriptEditorScreen.cpp +++ b/src/ScriptEditorScreen.cpp @@ -28,6 +28,7 @@ #include #include #include +#include using namespace GAGCore; #include #include @@ -569,7 +570,8 @@ void ScriptEditorScreen::loadSave(bool isLoad, const char *dir, const char *ext) while(loadSaveScreen->endValue<0) { int time = SDL_GetTicks(); - while (SDL_PollEvent(&event)) + EventListener *el = EventListener::instance(); + while (el->poll(&event)) { loadSaveScreen->translateAndProcessEvent(&event); } diff --git a/src/YOGClientGameConnectionDialog.cpp b/src/YOGClientGameConnectionDialog.cpp index 8d29c96f..93009ba9 100644 --- a/src/YOGClientGameConnectionDialog.cpp +++ b/src/YOGClientGameConnectionDialog.cpp @@ -24,6 +24,7 @@ #include #include "StringTable.h" #include "Toolkit.h" +#include "EventListener.h" using namespace GAGCore; using namespace GAGGUI; @@ -62,7 +63,8 @@ void YOGClientGameConnectionDialog::execute() while(endValue<0) { Sint32 time = SDL_GetTicks(); - while (SDL_PollEvent(&event)) + EventListener *el = EventListener::instance(); + while (el->poll(&event)) { if (event.type==SDL_QUIT) break;