a3c4b8e266d16174fd9576b84f11bf17aad132b9
[supertux.git] / src / gui / menu_manager.cpp
1 //  SuperTux
2 //  Copyright (C) 2009 Ingo Ruhnke <grumbel@gmail.com>
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/dialog.hpp"
23 #include "gui/menu.hpp"
24 #include "gui/mousecursor.hpp"
25 #include "math/sizef.hpp"
26 #include "supertux/globals.hpp"
27 #include "supertux/menu/menu_storage.hpp"
28 #include "supertux/timer.hpp"
29 #include "util/log.hpp"
30 #include "video/drawing_context.hpp"
31
32 MenuManager* MenuManager::s_instance = 0;
33
34 MenuManager&
35 MenuManager::instance()
36 {
37   assert(s_instance);
38   return *s_instance;
39 }
40
41 namespace {
42
43 Rectf menu2rect(const Menu& menu)
44 {
45   return Rectf(menu.get_center_pos().x - menu.get_width() / 2,
46                menu.get_center_pos().y - menu.get_height() / 2,
47                menu.get_center_pos().x + menu.get_width() / 2,
48                menu.get_center_pos().y + menu.get_height() / 2);
49 }
50
51 } // namespace
52
53 class MenuTransition
54 {
55 private:
56   Rectf m_from_rect;
57   Rectf m_to_rect;
58
59   float m_effect_progress;
60   float m_effect_start_time;
61   bool m_is_active;
62
63 public:
64   MenuTransition() :
65     m_from_rect(),
66     m_to_rect(),
67     m_effect_progress(1.0f),
68     m_effect_start_time(),
69     m_is_active(false)
70   {
71   }
72
73   void start(const Rectf& from_rect,
74              const Rectf& to_rect)
75   {
76     m_from_rect = from_rect;
77     m_to_rect = to_rect;
78
79     m_effect_start_time = real_time;
80     m_effect_progress = 0.0f;
81
82     m_is_active = true;
83   }
84
85   void set(const Rectf& rect)
86   {
87     m_to_rect = m_from_rect = rect;
88   }
89
90   void update()
91   {
92     if (m_is_active)
93     {
94       m_effect_progress = (real_time - m_effect_start_time) * 6.0f;
95
96       if (m_effect_progress > 1.0f)
97       {
98         m_effect_progress = 1.0f;
99         m_is_active = false;
100       }
101     }
102   }
103
104   void draw(DrawingContext& context)
105   {
106     float p = m_effect_progress;
107
108     Rectf rect = m_to_rect;
109     if (m_is_active)
110     {
111       rect.p1.x = (m_to_rect.p1.x * p) + (m_from_rect.p1.x * (1.0f - p));
112       rect.p1.y = (m_to_rect.p1.y * p) + (m_from_rect.p1.y * (1.0f - p));
113       rect.p2.x = (m_to_rect.p2.x * p) + (m_from_rect.p2.x * (1.0f - p));
114       rect.p2.y = (m_to_rect.p2.y * p) + (m_from_rect.p2.y * (1.0f - p));
115     }
116
117     // draw menu background rectangles
118     context.draw_filled_rect(Rectf(rect.p1.x - 4, rect.p1.y - 10-4,
119                                    rect.p2.x + 4, rect.p2.y + 10 + 4),
120                              Color(0.2f, 0.3f, 0.4f, 0.8f),
121                              20.0f,
122                              LAYER_GUI-10);
123
124     context.draw_filled_rect(Rectf(rect.p1.x, rect.p1.y - 10,
125                                    rect.p2.x, rect.p2.y + 10),
126                              Color(0.6f, 0.7f, 0.8f, 0.5f),
127                              16.0f,
128                              LAYER_GUI-10);
129   }
130
131   bool is_active()
132   {
133     return m_is_active;
134   }
135 };
136
137 MenuManager::MenuManager() :
138   m_dialog(),
139   m_has_next_dialog(false),
140   m_next_dialog(),
141   m_menu_stack(),
142   m_transition(new MenuTransition)
143 {
144   s_instance = this;
145 }
146
147 MenuManager::~MenuManager()
148 {
149   s_instance = nullptr;
150 }
151
152 void
153 MenuManager::refresh()
154 {
155   for(auto i = m_menu_stack.begin(); i != m_menu_stack.end(); ++i)
156   {
157     (*i)->refresh();
158   }
159 }
160
161 void
162 MenuManager::process_input()
163 {
164   if (m_dialog)
165   {
166     m_dialog->process_input(*InputManager::current()->get_controller());
167   }
168   else if (current_menu())
169   {
170     current_menu()->process_input();
171   }
172 }
173
174 void
175 MenuManager::event(const SDL_Event& ev)
176 {
177   if (!m_transition->is_active())
178   {
179     if (m_dialog)
180     {
181       m_dialog->event(ev);
182     }
183     else if (current_menu())
184     {
185       // only pass events when the menu is fully visible and not in a
186       // transition animation
187       current_menu()->event(ev);
188     }
189   }
190 }
191
192 void
193 MenuManager::draw(DrawingContext& context)
194 {
195   if (m_has_next_dialog)
196   {
197     m_dialog = std::move(m_next_dialog);
198     m_has_next_dialog = false;
199   }
200
201   if (m_transition->is_active())
202   {
203     m_transition->update();
204     m_transition->draw(context);
205   }
206   else
207   {
208     if (m_dialog)
209     {
210       m_dialog->update();
211       m_dialog->draw(context);
212     }
213     else if (current_menu())
214     {
215       // brute force the transition into the right shape in case the
216       // menu has changed sizes
217       m_transition->set(menu2rect(*current_menu()));
218       m_transition->draw(context);
219
220       current_menu()->draw(context);
221     }
222   }
223
224   if (current_menu() && MouseCursor::current())
225   {
226     MouseCursor::current()->draw(context);
227   }
228 }
229
230 void
231 MenuManager::set_dialog(std::unique_ptr<Dialog> dialog)
232 {
233   // delay reseting m_dialog to a later point, as otherwise the Dialog
234   // can't unset itself without ending up with "delete this" problems
235   m_next_dialog = std::move(dialog);
236   m_has_next_dialog = true;
237 }
238
239 void
240 MenuManager::push_menu(int id)
241 {
242   push_menu(MenuStorage::instance().create(static_cast<MenuStorage::MenuId>(id)));
243 }
244
245 void
246 MenuManager::set_menu(int id)
247 {
248   set_menu(MenuStorage::instance().create(static_cast<MenuStorage::MenuId>(id)));
249 }
250
251 void
252 MenuManager::push_menu(std::unique_ptr<Menu> menu)
253 {
254   assert(menu);
255   transition(m_menu_stack.empty() ? nullptr : m_menu_stack.back().get(),
256              menu.get());
257   m_menu_stack.push_back(std::move(menu));
258 }
259
260 void
261 MenuManager::pop_menu()
262 {
263   if (m_menu_stack.empty())
264   {
265     log_warning << "trying to pop on an empty menu_stack" << std::endl;
266   }
267   else
268   {
269     transition(m_menu_stack.back().get(),
270                (m_menu_stack.size() >= 2)
271                ? m_menu_stack[m_menu_stack.size() - 2].get()
272                : nullptr);
273
274     m_menu_stack.pop_back();
275   }
276 }
277
278 void
279 MenuManager::set_menu(std::unique_ptr<Menu> menu)
280 {
281   if (menu)
282   {
283     transition(m_menu_stack.empty() ? nullptr : m_menu_stack.back().get(),
284                menu.get());
285     m_menu_stack.clear();
286     m_menu_stack.push_back(std::move(menu));
287   }
288   else
289   {
290     transition(m_menu_stack.empty() ? nullptr : m_menu_stack.back().get(),
291                nullptr);
292     m_menu_stack.clear();
293   }
294
295   // just to be sure...
296   InputManager::current()->reset();
297 }
298
299 void
300 MenuManager::clear_menu_stack()
301 {
302   transition(m_menu_stack.empty() ? nullptr : m_menu_stack.back().get(),
303              nullptr);
304   m_menu_stack.clear();
305 }
306
307 void
308 MenuManager::on_window_resize()
309 {
310   for(auto i = m_menu_stack.begin(); i != m_menu_stack.end(); ++i)
311   {
312     (*i)->on_window_resize();
313   }
314 }
315
316 Menu*
317 MenuManager::current_menu() const
318 {
319   if (m_menu_stack.empty())
320   {
321     return nullptr;
322   }
323   else
324   {
325     return m_menu_stack.back().get();
326   }
327 }
328
329 void
330 MenuManager::transition(Menu* from, Menu* to)
331 {
332   if (!from && !to)
333   {
334     return;
335   }
336   else
337   {
338     Rectf from_rect;
339     if (from)
340     {
341       from_rect = menu2rect(*from);
342     }
343     else
344     {
345       from_rect = Rectf(to->get_center_pos(), Sizef(0, 0));
346     }
347
348     Rectf to_rect;
349     if (to)
350     {
351       to_rect = menu2rect(*to);
352     }
353     else
354     {
355       to_rect = Rectf(from->get_center_pos(), Sizef(0, 0));
356     }
357
358     m_transition->start(from_rect, to_rect);
359   }
360 }
361
362 /* EOF */