1bd6330aae3cec1330d2f4f07019d31319f1eea7
[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_menu_stack(),
140   m_transition(new MenuTransition)
141 {
142   s_instance = this;
143 }
144
145 MenuManager::~MenuManager()
146 {
147   s_instance = nullptr;
148 }
149
150 void
151 MenuManager::refresh()
152 {
153   for(auto i = m_menu_stack.begin(); i != m_menu_stack.end(); ++i)
154   {
155     (*i)->refresh();
156   }
157 }
158
159 void
160 MenuManager::process_input()
161 {
162   if (m_dialog)
163   {
164     m_dialog->process_input(*InputManager::current()->get_controller());
165   }
166   else if (current_menu())
167   {
168     current_menu()->process_input();
169   }
170 }
171
172 void
173 MenuManager::event(const SDL_Event& ev)
174 {
175   if (!m_transition->is_active())
176   {
177     if (m_dialog)
178     {
179       m_dialog->event(ev);
180     }
181     else if (current_menu())
182     {
183       // only pass events when the menu is fully visible and not in a
184       // transition animation
185       current_menu()->event(ev);
186     }
187   }
188 }
189
190 void
191 MenuManager::draw(DrawingContext& context)
192 {
193   if (m_transition->is_active())
194   {
195     m_transition->update();
196     m_transition->draw(context);
197   }
198   else
199   {
200     if (m_dialog)
201     {
202       m_dialog->draw(context);
203     }
204     else if (current_menu())
205     {
206       // brute force the transition into the right shape in case the
207       // menu has changed sizes
208       m_transition->set(menu2rect(*current_menu()));
209       m_transition->draw(context);
210
211       current_menu()->draw(context);
212     }
213   }
214
215   if (current_menu() && MouseCursor::current())
216   {
217     MouseCursor::current()->draw(context);
218   }
219 }
220
221 void
222 MenuManager::set_dialog(std::unique_ptr<Dialog> dialog)
223 {
224   m_dialog = std::move(dialog);
225 }
226
227 void
228 MenuManager::push_menu(int id)
229 {
230   push_menu(MenuStorage::instance().create(static_cast<MenuStorage::MenuId>(id)));
231 }
232
233 void
234 MenuManager::set_menu(int id)
235 {
236   set_menu(MenuStorage::instance().create(static_cast<MenuStorage::MenuId>(id)));
237 }
238
239 void
240 MenuManager::push_menu(std::unique_ptr<Menu> menu)
241 {
242   assert(menu);
243   transition(m_menu_stack.empty() ? nullptr : m_menu_stack.back().get(),
244              menu.get());
245   m_menu_stack.push_back(std::move(menu));
246 }
247
248 void
249 MenuManager::pop_menu()
250 {
251   if (m_menu_stack.empty())
252   {
253     log_warning << "trying to pop on an empty menu_stack" << std::endl;
254   }
255   else
256   {
257     transition(m_menu_stack.back().get(),
258                (m_menu_stack.size() >= 2)
259                ? m_menu_stack[m_menu_stack.size() - 2].get()
260                : nullptr);
261
262     m_menu_stack.pop_back();
263   }
264 }
265
266 void
267 MenuManager::set_menu(std::unique_ptr<Menu> menu)
268 {
269   if (menu)
270   {
271     transition(m_menu_stack.empty() ? nullptr : m_menu_stack.back().get(),
272                menu.get());
273     m_menu_stack.clear();
274     m_menu_stack.push_back(std::move(menu));
275   }
276   else
277   {
278     transition(m_menu_stack.empty() ? nullptr : m_menu_stack.back().get(),
279                nullptr);
280     m_menu_stack.clear();
281   }
282
283   // just to be sure...
284   InputManager::current()->reset();
285 }
286
287 void
288 MenuManager::clear_menu_stack()
289 {
290   transition(m_menu_stack.empty() ? nullptr : m_menu_stack.back().get(),
291              nullptr);
292   m_menu_stack.clear();
293 }
294
295 void
296 MenuManager::on_window_resize()
297 {
298   for(auto i = m_menu_stack.begin(); i != m_menu_stack.end(); ++i)
299   {
300     (*i)->on_window_resize();
301   }
302 }
303
304 Menu*
305 MenuManager::current_menu() const
306 {
307   if (m_menu_stack.empty())
308   {
309     return nullptr;
310   }
311   else
312   {
313     return m_menu_stack.back().get();
314   }
315 }
316
317 void
318 MenuManager::transition(Menu* from, Menu* to)
319 {
320   if (!from && !to)
321   {
322     return;
323   }
324   else
325   {
326     Rectf from_rect;
327     if (from)
328     {
329       from_rect = menu2rect(*from);
330     }
331     else
332     {
333       from_rect = Rectf(to->get_center_pos(), Sizef(0, 0));
334     }
335
336     Rectf to_rect;
337     if (to)
338     {
339       to_rect = menu2rect(*to);
340     }
341     else
342     {
343       to_rect = Rectf(from->get_center_pos(), Sizef(0, 0));
344     }
345
346     m_transition->start(from_rect, to_rect);
347   }
348 }
349
350 /* EOF */