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