A first try at error handling in the Downloader
[supertux.git] / src / gui / menu_manager.cpp
index fa0ae00..009a10a 100644 (file)
@@ -1,5 +1,5 @@
 //  SuperTux
-//  Copyright (C) 2009 Ingo Ruhnke <grumbel@gmx.de>
+//  Copyright (C) 2009 Ingo Ruhnke <grumbel@gmail.com>
 //
 //  This program is free software: you can redistribute it and/or modify
 //  it under the terms of the GNU General Public License as published by
 #include <assert.h>
 
 #include "control/input_manager.hpp"
+#include "gui/dialog.hpp"
 #include "gui/menu.hpp"
+#include "gui/mousecursor.hpp"
+#include "math/sizef.hpp"
 #include "supertux/globals.hpp"
+#include "supertux/menu/menu_storage.hpp"
 #include "supertux/timer.hpp"
+#include "util/gettext.hpp"
+#include "util/log.hpp"
+#include "video/drawing_context.hpp"
 
 MenuManager* MenuManager::s_instance = 0;
 
@@ -32,11 +39,108 @@ MenuManager::instance()
   return *s_instance;
 }
 
+namespace {
+
+Rectf menu2rect(const Menu& menu)
+{
+  return Rectf(menu.get_center_pos().x - menu.get_width() / 2,
+               menu.get_center_pos().y - menu.get_height() / 2,
+               menu.get_center_pos().x + menu.get_width() / 2,
+               menu.get_center_pos().y + menu.get_height() / 2);
+}
+
+} // namespace
+
+class MenuTransition
+{
+private:
+  Rectf m_from_rect;
+  Rectf m_to_rect;
+
+  float m_effect_progress;
+  float m_effect_start_time;
+  bool m_is_active;
+
+public:
+  MenuTransition() :
+    m_from_rect(),
+    m_to_rect(),
+    m_effect_progress(1.0f),
+    m_effect_start_time(),
+    m_is_active(false)
+  {
+  }
+
+  void start(const Rectf& from_rect,
+             const Rectf& to_rect)
+  {
+    m_from_rect = from_rect;
+    m_to_rect = to_rect;
+
+    m_effect_start_time = real_time;
+    m_effect_progress = 0.0f;
+
+    m_is_active = true;
+  }
+
+  void set(const Rectf& rect)
+  {
+    m_to_rect = m_from_rect = rect;
+  }
+
+  void update()
+  {
+    if (m_is_active)
+    {
+      m_effect_progress = (real_time - m_effect_start_time) * 6.0f;
+
+      if (m_effect_progress > 1.0f)
+      {
+        m_effect_progress = 1.0f;
+        m_is_active = false;
+      }
+    }
+  }
+
+  void draw(DrawingContext& context)
+  {
+    float p = m_effect_progress;
+
+    Rectf rect = m_to_rect;
+    if (m_is_active)
+    {
+      rect.p1.x = (m_to_rect.p1.x * p) + (m_from_rect.p1.x * (1.0f - p));
+      rect.p1.y = (m_to_rect.p1.y * p) + (m_from_rect.p1.y * (1.0f - p));
+      rect.p2.x = (m_to_rect.p2.x * p) + (m_from_rect.p2.x * (1.0f - p));
+      rect.p2.y = (m_to_rect.p2.y * p) + (m_from_rect.p2.y * (1.0f - p));
+    }
+
+    // draw menu background rectangles
+    context.draw_filled_rect(Rectf(rect.p1.x - 4, rect.p1.y - 10-4,
+                                   rect.p2.x + 4, rect.p2.y + 10 + 4),
+                             Color(0.2f, 0.3f, 0.4f, 0.8f),
+                             20.0f,
+                             LAYER_GUI-10);
+
+    context.draw_filled_rect(Rectf(rect.p1.x, rect.p1.y - 10,
+                                   rect.p2.x, rect.p2.y + 10),
+                             Color(0.6f, 0.7f, 0.8f, 0.5f),
+                             16.0f,
+                             LAYER_GUI-10);
+  }
+
+  bool is_active()
+  {
+    return m_is_active;
+  }
+};
+
 MenuManager::MenuManager() :
-  m_last_menus(),
-  m_all_menus(),
-  m_previous(),
-  m_current()
+  m_dialog(),
+  m_has_next_dialog(false),
+  m_next_dialog(),
+  m_menu_stack(),
+  m_transition(new MenuTransition)
 {
   s_instance = this;
 }
@@ -47,78 +151,222 @@ MenuManager::~MenuManager()
 }
 
 void
-MenuManager::push_current(Menu* menu)
+MenuManager::refresh()
 {
-  m_previous = m_current;
+  for(auto i = m_menu_stack.begin(); i != m_menu_stack.end(); ++i)
+  {
+    (*i)->refresh();
+  }
+}
 
-  if (m_current)
+void
+MenuManager::process_input()
+{
+  if (m_dialog)
+  {
+    m_dialog->process_input(*InputManager::current()->get_controller());
+  }
+  else if (current_menu())
   {
-    m_last_menus.push_back(m_current);
+    current_menu()->process_input();
   }
+}
 
-  m_current = menu;
-  m_current->effect_start_time = real_time;
-  m_current->effect_progress = 0.0f;
+void
+MenuManager::event(const SDL_Event& ev)
+{
+  if (!m_transition->is_active())
+  {
+    if (m_dialog)
+    {
+      m_dialog->event(ev);
+    }
+    else if (current_menu())
+    {
+      // only pass events when the menu is fully visible and not in a
+      // transition animation
+      current_menu()->event(ev);
+    }
+  }
 }
 
 void
-MenuManager::pop_current()
+MenuManager::draw(DrawingContext& context)
 {
-  m_previous = m_current;
+  if (m_has_next_dialog)
+  {
+    m_dialog = std::move(m_next_dialog);
+    m_has_next_dialog = false;
+  }
 
-  if (m_last_menus.size() >= 1)
+  if (m_transition->is_active())
   {
-    m_current = m_last_menus.back();
-    m_current->effect_start_time = real_time;
-    m_current->effect_progress   = 0.0f;
-    m_last_menus.pop_back();
+    m_transition->update();
+    m_transition->draw(context);
   }
   else
   {
-    set_current(nullptr);
+    if (m_dialog)
+    {
+      try
+      {
+        m_dialog->update();
+      }
+      catch(const std::exception& err)
+      {
+        m_dialog = std::unique_ptr<Dialog>(new Dialog);
+        m_dialog->set_text(_("Error:\n") + err.what());
+        m_dialog->add_button(_("Ok"));
+      }
+
+      m_dialog->draw(context);
+    }
+    else if (current_menu())
+    {
+      // brute force the transition into the right shape in case the
+      // menu has changed sizes
+      m_transition->set(menu2rect(*current_menu()));
+      m_transition->draw(context);
+
+      current_menu()->draw(context);
+    }
+  }
+
+  if (current_menu() && MouseCursor::current())
+  {
+    MouseCursor::current()->draw(context);
   }
 }
 
 void
-MenuManager::set_current(Menu* menu)
+MenuManager::set_dialog(std::unique_ptr<Dialog> dialog)
+{
+  // delay reseting m_dialog to a later point, as otherwise the Dialog
+  // can't unset itself without ending up with "delete this" problems
+  m_next_dialog = std::move(dialog);
+  m_has_next_dialog = true;
+}
+
+void
+MenuManager::push_menu(int id)
+{
+  push_menu(MenuStorage::instance().create(static_cast<MenuStorage::MenuId>(id)));
+}
+
+void
+MenuManager::set_menu(int id)
+{
+  set_menu(MenuStorage::instance().create(static_cast<MenuStorage::MenuId>(id)));
+}
+
+void
+MenuManager::push_menu(std::unique_ptr<Menu> menu)
 {
-  if (m_current && m_current->close == true)
+  assert(menu);
+  transition(m_menu_stack.empty() ? nullptr : m_menu_stack.back().get(),
+             menu.get());
+  m_menu_stack.push_back(std::move(menu));
+}
+
+void
+MenuManager::pop_menu()
+{
+  if (m_menu_stack.empty())
   {
-    // do nothing
+    log_warning << "trying to pop on an empty menu_stack" << std::endl;
   }
   else
   {
-    m_previous = m_current;
+    transition(m_menu_stack.back().get(),
+               (m_menu_stack.size() >= 2)
+               ? m_menu_stack[m_menu_stack.size() - 2].get()
+               : nullptr);
 
-    if (menu)
-    {
-      menu->effect_start_time = real_time;
-      menu->effect_progress = 0.0f;
-      m_current = menu;
-    }
-    else if (m_current)
-    {
-      m_last_menus.clear();                         //NULL new menu pointer => close all menus
-      m_current->effect_start_time = real_time;
-      m_current->effect_progress = 0.0f;
-      m_current->close = true;
-    }
+    m_menu_stack.pop_back();
+  }
+}
 
-    // just to be sure...
-    g_input_manager->reset();
+void
+MenuManager::set_menu(std::unique_ptr<Menu> menu)
+{
+  if (menu)
+  {
+    transition(m_menu_stack.empty() ? nullptr : m_menu_stack.back().get(),
+               menu.get());
+    m_menu_stack.clear();
+    m_menu_stack.push_back(std::move(menu));
   }
+  else
+  {
+    transition(m_menu_stack.empty() ? nullptr : m_menu_stack.back().get(),
+               nullptr);
+    m_menu_stack.clear();
+  }
+
+  // just to be sure...
+  InputManager::current()->reset();
 }
 
 void
-MenuManager::recalc_pos()
+MenuManager::clear_menu_stack()
 {
-  if (m_current)
-    m_current->set_pos(SCREEN_WIDTH/2, SCREEN_HEIGHT/2);
+  transition(m_menu_stack.empty() ? nullptr : m_menu_stack.back().get(),
+             nullptr);
+  m_menu_stack.clear();
+}
+
+void
+MenuManager::on_window_resize()
+{
+  for(auto i = m_menu_stack.begin(); i != m_menu_stack.end(); ++i)
+  {
+    (*i)->on_window_resize();
+  }
+}
+
+Menu*
+MenuManager::current_menu() const
+{
+  if (m_menu_stack.empty())
+  {
+    return nullptr;
+  }
+  else
+  {
+    return m_menu_stack.back().get();
+  }
+}
 
-  for(auto i = m_all_menus.begin(); i != m_all_menus.end(); ++i)
+void
+MenuManager::transition(Menu* from, Menu* to)
+{
+  if (!from && !to)
+  {
+    return;
+  }
+  else
   {
-    // FIXME: This is of course not quite right, since it ignores any previous set_pos() calls
-    (*i)->set_pos(SCREEN_WIDTH/2, SCREEN_HEIGHT/2);
+    Rectf from_rect;
+    if (from)
+    {
+      from_rect = menu2rect(*from);
+    }
+    else
+    {
+      from_rect = Rectf(to->get_center_pos(), Sizef(0, 0));
+    }
+
+    Rectf to_rect;
+    if (to)
+    {
+      to_rect = menu2rect(*to);
+    }
+    else
+    {
+      to_rect = Rectf(from->get_center_pos(), Sizef(0, 0));
+    }
+
+    m_transition->start(from_rect, to_rect);
   }
 }