Renamed MenuManager::recalc_pos() -> MenuManager::on_window_resize() and Menu::update...
[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::refresh()
145 {
146   for(auto i = m_menu_stack.begin(); i != m_menu_stack.end(); ++i)
147   {
148     (*i)->refresh();
149   }
150 }
151
152 void
153 MenuManager::process_input()
154 {
155   if (current())
156   {
157     current()->process_input();
158   }
159 }
160
161 void
162 MenuManager::event(const SDL_Event& event)
163 {
164   if (current() && !m_transition->is_active())
165   {
166     // only pass events when the menu is fully visible and not in a
167     // transition animation
168     current()->event(event);
169   }
170 }
171
172 void
173 MenuManager::draw(DrawingContext& context)
174 {
175   if (m_transition->is_active() || current())
176   {
177     m_transition->update();
178     m_transition->draw(context);
179   }
180
181   if (current())
182   {
183     if (!m_transition->is_active())
184     {
185       current()->draw(context);
186     }
187
188     if (MouseCursor::current())
189     {
190       MouseCursor::current()->draw(context);
191     }
192   }
193 }
194
195 bool
196 MenuManager::check_menu()
197 {
198   if (current())
199   {
200     current()->check_menu();
201     return true;
202   }
203   else
204   {
205     return false;
206   }
207 }
208
209 void
210 MenuManager::push_menu(int id)
211 {
212   push_menu(MenuStorage::instance().create(static_cast<MenuStorage::MenuId>(id)));
213 }
214
215 void
216 MenuManager::set_menu(int id)
217 {
218   set_menu(MenuStorage::instance().create(static_cast<MenuStorage::MenuId>(id)));
219 }
220
221 void
222 MenuManager::push_menu(std::unique_ptr<Menu> menu)
223 {
224   assert(menu);
225   transition(m_menu_stack.empty() ? nullptr : m_menu_stack.back().get(),
226              menu.get());
227   m_menu_stack.push_back(std::move(menu));
228 }
229
230 void
231 MenuManager::pop_menu()
232 {
233   if (m_menu_stack.empty())
234   {
235     log_warning << "trying to pop on an empty menu_stack" << std::endl;
236   }
237   else
238   {
239     transition(m_menu_stack.back().get(),
240                (m_menu_stack.size() >= 2)
241                ? m_menu_stack[m_menu_stack.size() - 2].get()
242                : nullptr);
243
244     m_menu_stack.pop_back();
245   }
246 }
247
248 void
249 MenuManager::set_menu(std::unique_ptr<Menu> menu)
250 {
251   if (menu)
252   {
253     transition(m_menu_stack.empty() ? nullptr : m_menu_stack.back().get(),
254                menu.get());
255     m_menu_stack.push_back(std::move(menu));
256   }
257   else
258   {
259     transition(m_menu_stack.empty() ? nullptr : m_menu_stack.back().get(),
260                nullptr);
261     m_menu_stack.clear();
262   }
263
264   // just to be sure...
265   g_input_manager->reset();
266 }
267
268 void
269 MenuManager::clear_menu_stack()
270 {
271   transition(m_menu_stack.empty() ? nullptr : m_menu_stack.back().get(),
272              nullptr);
273   m_menu_stack.clear();
274 }
275
276 void
277 MenuManager::on_window_resize()
278 {
279   for(auto i = m_menu_stack.begin(); i != m_menu_stack.end(); ++i)
280   {
281     // FIXME: This is of course not quite right, since it ignores any
282     // previous set_pos() calls, it also doesn't update the
283     // transition-effect/background rectangle
284     (*i)->set_pos(SCREEN_WIDTH/2, SCREEN_HEIGHT/2);
285   }
286 }
287
288 Menu*
289 MenuManager::current() const
290 {
291   if (m_menu_stack.empty())
292   {
293     return nullptr;
294   }
295   else
296   {
297     return m_menu_stack.back().get();
298   }
299 }
300
301 void
302 MenuManager::transition(Menu* from, Menu* to)
303 {
304   if (!from && !to)
305   {
306     return;
307   }
308   else
309   {
310     Rectf from_rect;
311     if (from)
312     {
313       from_rect = menu2rect(*from);
314     }
315     else
316     {
317       from_rect = Rectf(to->get_pos(), Sizef(0, 0));
318     }
319
320     Rectf to_rect;
321     if (to)
322     {
323       to_rect = menu2rect(*to);
324     }
325     else
326     {
327       to_rect = Rectf(from->get_pos(), Sizef(0, 0));
328     }
329
330     m_transition->start(from_rect, to_rect);
331   }
332 }
333
334 /* EOF */