1 //  SuperTuxKart - a fun racing game with go-kart
2 //  Copyright (C) 2009-2015 Marianne Gagnon
3 //
4 //  This program is free software; you can redistribute it and/or
5 //  modify it under the terms of the GNU General Public License
6 //  as published by the Free Software Foundation; either version 3
7 //  of the License, or (at your option) any later version.
8 //
9 //  This program is distributed in the hope that it will be useful,
10 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
11 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 //  GNU General Public License for more details.
13 //
14 //  You should have received a copy of the GNU General Public License
15 //  along with this program; if not, write to the Free Software
16 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
17 
18 
19 #include "guiengine/abstract_state_manager.hpp"
20 
21 #include "config/user_config.hpp"
22 #include "guiengine/engine.hpp"
23 #include "guiengine/modaldialog.hpp"
24 #include "guiengine/screen_keyboard.hpp"
25 #include "guiengine/screen.hpp"
26 #include "input/device_manager.hpp"
27 #include "utils/debug.hpp"
28 
29 #include <vector>
30 #include <iostream>
31 #include <IGUIEnvironment.h>
32 
33 using namespace GUIEngine;
34 
35 
36 static const char RACE_STATE_NAME[] = "race";
37 
AbstractStateManager()38 AbstractStateManager::AbstractStateManager()
39 {
40     m_game_mode = MENU;
41 }   // AbstractStateManager
42 
43 #if 0
44 #pragma mark -
45 #pragma mark Game State Management
46 #endif
47 
48 // ----------------------------------------------------------------------------
49 
enterGameState()50 void AbstractStateManager::enterGameState()
51 {
52     if (GUIEngine::isNoGraphics())
53     {
54         // No graphics STK won't push dialog
55         setGameState(GAME);
56         return;
57     }
58 
59      // you need to close any dialog before calling this
60     assert(!ModalDialog::isADialogActive());
61     assert(!ScreenKeyboard::isActive());
62 
63     if (getCurrentScreen() != NULL) getCurrentScreen()->tearDown();
64     m_menu_stack.clear();
65     m_menu_stack.emplace_back(RACE_STATE_NAME, (Screen*)NULL);
66 
67     Debug::closeDebugMenu();
68     setGameState(GAME);
69     GUIEngine::cleanForGame();
70 }   // enterGameState
71 
72 // ----------------------------------------------------------------------------
73 
getGameState()74 GameState AbstractStateManager::getGameState()
75 {
76     return m_game_mode;
77 }   // getGameState
78 
79 // ----------------------------------------------------------------------------
80 
setGameState(GameState state)81 void AbstractStateManager::setGameState(GameState state)
82 {
83     if (m_game_mode == state) return; // no change
84 
85     m_game_mode = state;
86 
87     onGameStateChange(state);
88 }   // setGameState
89 
90 
91 // ----------------------------------------------------------------------------
92 
93 #if 0
94 #pragma mark -
95 #pragma mark Push/pop menus
96 #endif
97 
pushMenu(Screen * screen)98 void AbstractStateManager::pushMenu(Screen* screen)
99 {
100     // currently, only a single in-game menu is supported
101     assert(m_game_mode != INGAME_MENU);
102 
103     // you need to close any dialog before calling this
104     assert(!ModalDialog::isADialogActive());
105     assert(!ScreenKeyboard::isActive());
106 
107     if (UserConfigParams::logGUI())
108     {
109         Log::info("AbstractStateManager::pushMenu", "Switching to screen %s",
110             screen->getName().c_str());
111     }
112 
113     // Send tear-down event to previous menu
114     if (m_menu_stack.size() > 0 && m_game_mode != GAME)
115         getCurrentScreen()->tearDown();
116 
117     m_menu_stack.emplace_back(screen->getName(), screen);
118     if (m_game_mode == GAME)
119     {
120         setGameState(INGAME_MENU);
121     }
122     else
123     {
124         setGameState(MENU);
125     }
126     switchToScreen(screen);
127 
128     onTopMostScreenChanged();
129 }   // pushMenu
130 
131 
132 // ----------------------------------------------------------------------------
133 
pushScreen(Screen * screen)134 void AbstractStateManager::pushScreen(Screen* screen)
135 {
136     // you need to close any dialog before calling this
137     assert(!ModalDialog::isADialogActive());
138     assert(!ScreenKeyboard::isActive());
139 
140     if (UserConfigParams::logGUI())
141     {
142         Log::info("AbstractStateManager::pushScreen", "Switching to screen %s",
143             screen->getName().c_str());
144     }
145 
146     if (!screen->isLoaded()) screen->loadFromFile();
147     pushMenu(screen);
148     screen->init();
149 
150     onTopMostScreenChanged();
151 }   // pushScreen%
152 
153 // ----------------------------------------------------------------------------
154 
replaceTopMostScreen(Screen * screen,GUIEngine::GameState gameState)155 void AbstractStateManager::replaceTopMostScreen(Screen* screen, GUIEngine::GameState gameState)
156 {
157     if (gameState == GUIEngine::CURRENT)
158         gameState = getGameState();
159 
160     //assert(m_game_mode != GAME);
161     // you need to close any dialog before calling this
162     assert(!ModalDialog::isADialogActive());
163     assert(!ScreenKeyboard::isActive());
164 
165     if (!screen->isLoaded()) screen->loadFromFile();
166     std::string name = screen->getName();
167 
168     if (UserConfigParams::logGUI())
169     {
170         Log::info("AbstractStateManager::replaceTopMostScreen", "Switching to screen %s",
171             name.c_str());
172     }
173 
174     assert(m_menu_stack.size() > 0);
175 
176     // Send tear-down event to previous menu
177     if (getCurrentScreen() != NULL)
178         getCurrentScreen()->tearDown();
179 
180     m_menu_stack[m_menu_stack.size()-1] = std::make_pair(name, screen);
181     setGameState(gameState);
182     switchToScreen(screen);
183 
184     // Send init event to new menu
185     getCurrentScreen()->init();
186 
187     onTopMostScreenChanged();
188 }   // replaceTopMostScreen
189 
190 // ----------------------------------------------------------------------------
191 
reshowTopMostMenu()192 void AbstractStateManager::reshowTopMostMenu()
193 {
194     assert(m_game_mode != GAME);
195     // you need to close any dialog before calling this
196     assert(!ModalDialog::isADialogActive());
197     assert(!ScreenKeyboard::isActive());
198 
199     // Send tear-down event to previous menu
200     if (m_menu_stack.size() > 0)
201     {
202         Screen* currScreen = getCurrentScreen();
203         if (currScreen != NULL) getCurrentScreen()->tearDown();
204     }
205 
206     switchToScreen(m_menu_stack[m_menu_stack.size()-1].second);
207 
208     // Send init event to new menu
209     Screen* screen = getCurrentScreen();
210     if (!screen->isLoaded()) screen->loadFromFile();
211     screen->init();
212 
213     onTopMostScreenChanged();
214 }   // reshowTopMostMenu
215 
216 // ----------------------------------------------------------------------------
217 
popMenu()218 void AbstractStateManager::popMenu()
219 {
220     assert(m_game_mode != GAME);
221 
222     if (m_menu_stack.empty())
223         return;
224 
225     // Send tear-down event to menu
226     getCurrentScreen()->tearDown();
227     m_menu_stack.pop_back();
228 
229     if (m_menu_stack.empty())
230     {
231         getGUIEnv()->clear();
232         getCurrentScreen()->elementsWereDeleted();
233         onStackEmptied();
234         return;
235     }
236 
237     if (UserConfigParams::logGUI())
238     {
239         Log::info("AbstractStateManager::popMenu", "Switching to screen %s",
240             m_menu_stack[m_menu_stack.size()-1].first.c_str());
241     }
242 
243     if (m_menu_stack[m_menu_stack.size()-1].first == RACE_STATE_NAME)
244     {
245         setGameState(GAME);
246         GUIEngine::cleanForGame();
247     }
248     else
249     {
250         setGameState(MENU);
251         switchToScreen(m_menu_stack[m_menu_stack.size()-1].second);
252 
253         Screen* screen = getCurrentScreen();
254         if (!screen->isLoaded()) screen->loadFromFile();
255         screen->init();
256     }
257 
258     onTopMostScreenChanged();
259 }   // popMenu
260 
261 // ----------------------------------------------------------------------------
262 
resetAndGoToScreen(Screen * screen)263 void AbstractStateManager::resetAndGoToScreen(Screen* screen)
264 {
265     // you need to close any dialog before calling this
266     assert(!ModalDialog::isADialogActive());
267     assert(!ScreenKeyboard::isActive());
268 
269     std::string name = screen->getName();
270 
271     if (UserConfigParams::logGUI())
272         Log::info("AbstractStateManager::resetAndGoToScreen", "Switching to screen %s",
273             name.c_str());
274 
275     if (m_game_mode != GAME) getCurrentScreen()->tearDown();
276     m_menu_stack.clear();
277 
278     if (!screen->isLoaded()) screen->loadFromFile();
279     m_menu_stack.emplace_back(name, screen);
280     setGameState(MENU);
281 
282     switchToScreen(screen);
283     getCurrentScreen()->init();
284 
285     onTopMostScreenChanged();
286 }   // resetAndGoToScreen
287 
288 // ----------------------------------------------------------------------------
289 
resetAndSetStack(Screen * screens[])290 void AbstractStateManager::resetAndSetStack(Screen* screens[])
291 {
292     assert(screens != NULL);
293     assert(screens[0] != NULL);
294     // you need to close any dialog before calling this
295     assert(!ModalDialog::isADialogActive());
296     assert(!ScreenKeyboard::isActive());
297 
298     if (m_game_mode != GAME && getCurrentScreen())
299         getCurrentScreen()->tearDown();
300     m_menu_stack.clear();
301 
302     for (int n=0; screens[n] != NULL; n++)
303     {
304         m_menu_stack.emplace_back(screens[n]->getName(), screens[n]);
305     }
306 
307     setGameState(MENU);
308 
309     switchToScreen(m_menu_stack[m_menu_stack.size()-1].second);
310     getCurrentScreen()->init();
311 
312     onTopMostScreenChanged();
313 }   // resetAndSetStack
314 
315 // ----------------------------------------------------------------------------
316 
onResize()317 void AbstractStateManager::onResize()
318 {
319     // Happens in the first resize in main.cpp
320     if (m_menu_stack.empty())
321         return;
322 
323     // In game resizing
324     if (m_menu_stack[0].first == RACE_STATE_NAME)
325     {
326         if (m_menu_stack.size() == 1)
327         {
328             clearScreenCache();
329             m_menu_stack.emplace_back(RACE_STATE_NAME, (Screen*)NULL);
330         }
331         return;
332     }
333 
334     // For some window manager it sends resize event when STK is not focus
335     // even if the screen is not resizable, prevent it from resizing if wrong
336     // screen
337     if (!m_menu_stack.back().second ||
338         !m_menu_stack.back().second->isResizable())
339         return;
340 
341     std::vector<std::function<Screen*()> > screen_function;
342     for (auto& p : m_menu_stack)
343         screen_function.push_back(p.second->getNewScreenPointer());
344     clearScreenCache();
345     std::vector<Screen*> new_screen;
346     for (auto& screen : screen_function)
347         new_screen.push_back(screen());
348     new_screen.push_back(NULL);
349     resetAndSetStack(new_screen.data());
350 }   // onResize
351