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