Added AddonDialog and connected it into the whole non-blocking update stuff
[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->update();
203       m_dialog->draw(context);
204     }
205     else if (current_menu())
206     {
207       // brute force the transition into the right shape in case the
208       // menu has changed sizes
209       m_transition->set(menu2rect(*current_menu()));
210       m_transition->draw(context);
211
212       current_menu()->draw(context);
213     }
214   }
215
216   if (current_menu() && MouseCursor::current())
217   {
218     MouseCursor::current()->draw(context);
219   }
220 }
221
222 void
223 MenuManager::set_dialog(std::unique_ptr<Dialog> dialog)
224 {
225   m_dialog = std::move(dialog);
226 }
227
228 void
229 MenuManager::push_menu(int id)
230 {
231   push_menu(MenuStorage::instance().create(static_cast<MenuStorage::MenuId>(id)));
232 }
233
234 void
235 MenuManager::set_menu(int id)
236 {
237   set_menu(MenuStorage::instance().create(static_cast<MenuStorage::MenuId>(id)));
238 }
239
240 void
241 MenuManager::push_menu(std::unique_ptr<Menu> menu)
242 {
243   assert(menu);
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
249 void
250 MenuManager::pop_menu()
251 {
252   if (m_menu_stack.empty())
253   {
254     log_warning << "trying to pop on an empty menu_stack" << std::endl;
255   }
256   else
257   {
258     transition(m_menu_stack.back().get(),
259                (m_menu_stack.size() >= 2)
260                ? m_menu_stack[m_menu_stack.size() - 2].get()
261                : nullptr);
262
263     m_menu_stack.pop_back();
264   }
265 }
266
267 void
268 MenuManager::set_menu(std::unique_ptr<Menu> menu)
269 {
270   if (menu)
271   {
272     transition(m_menu_stack.empty() ? nullptr : m_menu_stack.back().get(),
273                menu.get());
274     m_menu_stack.clear();
275     m_menu_stack.push_back(std::move(menu));
276   }
277   else
278   {
279     transition(m_menu_stack.empty() ? nullptr : m_menu_stack.back().get(),
280                nullptr);
281     m_menu_stack.clear();
282   }
283
284   // just to be sure...
285   InputManager::current()->reset();
286 }
287
288 void
289 MenuManager::clear_menu_stack()
290 {
291   transition(m_menu_stack.empty() ? nullptr : m_menu_stack.back().get(),
292              nullptr);
293   m_menu_stack.clear();
294 }
295
296 void
297 MenuManager::on_window_resize()
298 {
299   for(auto i = m_menu_stack.begin(); i != m_menu_stack.end(); ++i)
300   {
301     (*i)->on_window_resize();
302   }
303 }
304
305 Menu*
306 MenuManager::current_menu() const
307 {
308   if (m_menu_stack.empty())
309   {
310     return nullptr;
311   }
312   else
313   {
314     return m_menu_stack.back().get();
315   }
316 }
317
318 void
319 MenuManager::transition(Menu* from, Menu* to)
320 {
321   if (!from && !to)
322   {
323     return;
324   }
325   else
326   {
327     Rectf from_rect;
328     if (from)
329     {
330       from_rect = menu2rect(*from);
331     }
332     else
333     {
334       from_rect = Rectf(to->get_center_pos(), Sizef(0, 0));
335     }
336
337     Rectf to_rect;
338     if (to)
339     {
340       to_rect = menu2rect(*to);
341     }
342     else
343     {
344       to_rect = Rectf(from->get_center_pos(), Sizef(0, 0));
345     }
346
347     m_transition->start(from_rect, to_rect);
348   }
349 }
350
351 /* EOF */