c531c2e240852bbe5fb3867b959136bd64c7a7d3
[supertux.git] / src / gui / menu_manager.cpp
1 //  SuperTux
2 //  Copyright (C) 2009 Ingo Ruhnke <grumbel@gmx.de>
3 //
4 //  This program is free software: you can redistribute it and/or modify
5 //  it under the terms of the GNU General Public License as published by
6 //  the Free Software Foundation, either version 3 of the License, or
7 //  (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, see <http://www.gnu.org/licenses/>.
16
17 #include "gui/menu_manager.hpp"
18
19 #include <assert.h>
20
21 #include "control/input_manager.hpp"
22 #include "gui/menu.hpp"
23 #include "gui/mousecursor.hpp"
24 #include "math/sizef.hpp"
25 #include "supertux/globals.hpp"
26 #include "supertux/menu/menu_storage.hpp"
27 #include "supertux/timer.hpp"
28 #include "util/log.hpp"
29 #include "video/drawing_context.hpp"
30
31 MenuManager* MenuManager::s_instance = 0;
32
33 MenuManager&
34 MenuManager::instance()
35 {
36   assert(s_instance);
37   return *s_instance;
38 }
39
40 namespace {
41
42 Rectf menu2rect(const Menu& menu)
43 {
44   return Rectf(menu.get_pos().x - menu.get_width() / 2,
45                menu.get_pos().y - menu.get_height() / 2,
46                menu.get_pos().x + menu.get_width() / 2,
47                menu.get_pos().y + menu.get_height() / 2);
48 }
49
50 } // namespace
51
52 class MenuTransition
53 {
54 private:
55   Rectf m_from_rect;
56   Rectf m_to_rect;
57
58   float m_effect_progress;
59   float m_effect_start_time;
60   bool m_is_active;
61
62 public:
63   MenuTransition() :
64     m_from_rect(),
65     m_to_rect(),
66     m_effect_progress(1.0f),
67     m_effect_start_time(),
68     m_is_active(false)
69   {
70   }
71
72   void start(const Rectf& from_rect,
73              const Rectf& to_rect)
74   {
75     m_from_rect = from_rect;
76     m_to_rect = to_rect;
77
78     m_effect_start_time = real_time;
79     m_effect_progress = 0.0f;
80
81     m_is_active = true;
82   }
83
84   void update()
85   {
86     if (m_is_active)
87     {
88       m_effect_progress = (real_time - m_effect_start_time) * 6.0f;
89
90       if (m_effect_progress > 1.0f)
91       {
92         m_effect_progress = 1.0f;
93         m_is_active = false;
94       }
95     }
96   }
97
98   void draw(DrawingContext& context)
99   {
100     float p = m_effect_progress;
101
102     Rectf rect = m_to_rect;
103     if (m_is_active)
104     {
105       rect.p1.x = (m_to_rect.p1.x * p) + (m_from_rect.p1.x * (1.0f - p));
106       rect.p1.y = (m_to_rect.p1.y * p) + (m_from_rect.p1.y * (1.0f - p));
107       rect.p2.x = (m_to_rect.p2.x * p) + (m_from_rect.p2.x * (1.0f - p));
108       rect.p2.y = (m_to_rect.p2.y * p) + (m_from_rect.p2.y * (1.0f - p));
109     }
110
111     // draw menu background rectangles
112     context.draw_filled_rect(Rectf(rect.p1.x - 4, rect.p1.y - 10-4,
113                                    rect.p2.x + 4, rect.p2.y + 10 + 4),
114                              Color(0.2f, 0.3f, 0.4f, 0.8f),
115                              20.0f,
116                              LAYER_GUI-10);
117
118     context.draw_filled_rect(Rectf(rect.p1.x, rect.p1.y - 10,
119                                    rect.p2.x, rect.p2.y + 10),
120                              Color(0.6f, 0.7f, 0.8f, 0.5f),
121                              16.0f,
122                              LAYER_GUI-10);
123   }
124
125   bool is_active()
126   {
127     return m_is_active;
128   }
129 };
130
131 MenuManager::MenuManager() :
132   m_menu_stack(),
133   m_transition(new MenuTransition)
134 {
135   s_instance = this;
136 }
137
138 MenuManager::~MenuManager()
139 {
140   s_instance = nullptr;
141 }
142
143 void
144 MenuManager::update()
145 {
146   if (current())
147   {
148     current()->update();
149   }
150 }
151
152 void
153 MenuManager::event(const SDL_Event& event)
154 {
155   if (current() && !m_transition->is_active())
156   {
157     // only pass events when the menu is fully visible and not in a
158     // transition animation
159     current()->event(event);
160   }
161 }
162
163 void
164 MenuManager::draw(DrawingContext& context)
165 {
166   if (m_transition->is_active() || current())
167   {
168     m_transition->update();
169     m_transition->draw(context);
170   }
171
172   if (current())
173   {
174     if (!m_transition->is_active())
175     {
176       current()->draw(context);
177     }
178
179     if (MouseCursor::current())
180     {
181       MouseCursor::current()->draw(context);
182     }
183   }
184 }
185
186 bool
187 MenuManager::check_menu()
188 {
189   if (current())
190   {
191     current()->check_menu();
192     return true;
193   }
194   else
195   {
196     return false;
197   }
198 }
199
200 void
201 MenuManager::push_menu(int id)
202 {
203   push_menu(MenuStorage::instance().create(static_cast<MenuStorage::MenuId>(id)));
204 }
205
206 void
207 MenuManager::set_menu(int id)
208 {
209   set_menu(MenuStorage::instance().create(static_cast<MenuStorage::MenuId>(id)));
210 }
211
212 void
213 MenuManager::push_menu(std::unique_ptr<Menu> menu)
214 {
215   assert(menu);
216   transition(m_menu_stack.empty() ? nullptr : m_menu_stack.back().get(),
217              menu.get());
218   m_menu_stack.push_back(std::move(menu));
219 }
220
221 void
222 MenuManager::pop_menu()
223 {
224   if (m_menu_stack.empty())
225   {
226     log_warning << "trying to pop on an empty menu_stack" << std::endl;
227   }
228   else
229   {
230     transition(m_menu_stack.back().get(),
231                (m_menu_stack.size() >= 2)
232                ? m_menu_stack[m_menu_stack.size() - 2].get()
233                : nullptr);
234
235     m_menu_stack.pop_back();
236   }
237 }
238
239 void
240 MenuManager::set_menu(std::unique_ptr<Menu> menu)
241 {
242   if (menu)
243   {
244     transition(m_menu_stack.empty() ? nullptr : m_menu_stack.back().get(),
245                menu.get());
246     m_menu_stack.push_back(std::move(menu));
247   }
248   else
249   {
250     transition(m_menu_stack.empty() ? nullptr : m_menu_stack.back().get(),
251                nullptr);
252     m_menu_stack.clear();
253   }
254
255   // just to be sure...
256   g_input_manager->reset();
257 }
258
259 void
260 MenuManager::clear_menu_stack()
261 {
262   transition(m_menu_stack.empty() ? nullptr : m_menu_stack.back().get(),
263              nullptr);
264   m_menu_stack.clear();
265 }
266
267 void
268 MenuManager::recalc_pos()
269 {
270   for(auto i = m_menu_stack.begin(); i != m_menu_stack.end(); ++i)
271   {
272     // FIXME: This is of course not quite right, since it ignores any
273     // previous set_pos() calls, it also doesn't update the
274     // transition-effect/background rectangle
275     (*i)->set_pos(SCREEN_WIDTH/2, SCREEN_HEIGHT/2);
276   }
277 }
278
279 Menu*
280 MenuManager::current() const
281 {
282   if (m_menu_stack.empty())
283   {
284     return nullptr;
285   }
286   else
287   {
288     return m_menu_stack.back().get();
289   }
290 }
291
292 void
293 MenuManager::transition(Menu* from, Menu* to)
294 {
295   if (!from && !to)
296   {
297     return;
298   }
299   else
300   {
301     Rectf from_rect;
302     if (from)
303     {
304       from_rect = menu2rect(*from);
305     }
306     else
307     {
308       from_rect = Rectf(to->get_pos(), Sizef(0, 0));
309     }
310
311     Rectf to_rect;
312     if (to)
313     {
314       to_rect = menu2rect(*to);
315     }
316     else
317     {
318       to_rect = Rectf(from->get_pos(), Sizef(0, 0));
319     }
320
321     m_transition->start(from_rect, to_rect);
322   }
323 }
324
325 /* EOF */