From fce37403bbc29521b2b5bd983291b3730f8ad7b4 Mon Sep 17 00:00:00 2001
From: Star Rauchenberger <fefferburbia@gmail.com>
Date: Sat, 13 Mar 2021 12:24:05 -0500
Subject: Added submenus

#7
---
 res/sfx/menu_deselect.wav | Bin 0 -> 29480 bytes
 src/menu.cpp              |  23 ++++++++---
 src/menu.h                |  19 +++++++++-
 src/menu_system.cpp       |  95 +++++++++++++++++++++++++++++-----------------
 src/menu_system.h         |  24 +++++++++---
 src/renderer.cpp          |  26 +++++++++----
 src/renderer.h            |   3 +-
 7 files changed, 137 insertions(+), 53 deletions(-)
 create mode 100644 res/sfx/menu_deselect.wav

diff --git a/res/sfx/menu_deselect.wav b/res/sfx/menu_deselect.wav
new file mode 100644
index 0000000..800f84f
Binary files /dev/null and b/res/sfx/menu_deselect.wav differ
diff --git a/src/menu.cpp b/src/menu.cpp
index 4da4574..cfb0dd6 100644
--- a/src/menu.cpp
+++ b/src/menu.cpp
@@ -1,12 +1,25 @@
 #include "menu.h"
 
-std::vector<MenuItem> CreateMenu(const std::vector<MenuBuilder>& builders) {
-  std::vector<MenuItem> result;
-  result.reserve(builders.size());
+Menu::Menu(const std::vector<MenuBuilder>& builders) {
+  items_.reserve(builders.size());
 
   for (const MenuBuilder& builder : builders) {
-    result.push_back(builder.Build());
+    items_.push_back(builder.Build());
   }
+}
+
+void Menu::moveCursorUp() {
+  cursor_--;
 
-  return result;
+  if (cursor_ < 0) {
+    cursor_ = items_.size() - 1;
+  }
+}
+
+void Menu::moveCursorDown() {
+  cursor_++;
+
+  if (cursor_ >= items_.size()) {
+    cursor_ = 0;
+  }
 }
diff --git a/src/menu.h b/src/menu.h
index 528f8c8..67c75c0 100644
--- a/src/menu.h
+++ b/src/menu.h
@@ -48,6 +48,23 @@ private:
   MenuItem result_;
 };
 
-std::vector<MenuItem> CreateMenu(const std::vector<MenuBuilder>& builders);
+class Menu {
+public:
+
+  explicit Menu(const std::vector<MenuBuilder>& builders);
+
+  const std::vector<MenuItem>& getItems() const { return items_; }
+
+  int getCursorPosition() const { return cursor_; }
+
+  void moveCursorUp();
+
+  void moveCursorDown();
+
+private:
+
+  std::vector<MenuItem> items_;
+  int cursor_ = 0;
+};
 
 #endif /* end of include guard: MENU_H_3F6E62B3 */
diff --git a/src/menu_system.cpp b/src/menu_system.cpp
index 7631e4d..7ac8af5 100644
--- a/src/menu_system.cpp
+++ b/src/menu_system.cpp
@@ -4,10 +4,12 @@
 
 void MenuSystem::tick(double dt) {
   pauseAnimation_.tick(dt);
+  menuChangeAnimation_.tick(dt);
 
   if (openState_ == OpenState::Animating && pauseAnimation_.isComplete()) {
     if (pauseAnimation_.getProgress() == 0.0) {
       openState_ = OpenState::Closed;
+      menus_.clear();
 
       game_.unpauseGameplay();
       game_.getMixer().unpauseSounds();
@@ -15,20 +17,28 @@ void MenuSystem::tick(double dt) {
       openState_ = OpenState::Open;
     }
   } else if (openState_ == OpenState::Open) {
-    cursorBobTimer_.accumulate(dt);
-    while (cursorBobTimer_.step()) {
-      if (cursorBobDown_) {
-        cursorBob_++;
-
-        if (cursorBob_ >= 4) {
-          cursorBobDown_ = false;
+    if (!isMenuChanging_) {
+      cursorBobTimer_.accumulate(dt);
+      while (cursorBobTimer_.step()) {
+        if (cursorBobDown_) {
+          cursorBob_++;
+
+          if (cursorBob_ >= 4) {
+            cursorBobDown_ = false;
+          }
+        } else {
+          cursorBob_--;
+
+          if (cursorBob_ <= 0) {
+            cursorBobDown_ = true;
+          }
         }
-      } else {
-        cursorBob_--;
+      }
+    } else if (menuChangeAnimation_.isComplete()) {
+      isMenuChanging_ = false;
 
-        if (cursorBob_ <= 0) {
-          cursorBobDown_ = true;
-        }
+      if (menuChangeAnimation_.getProgress() == 0.0) {
+        menus_.pop_back();
       }
     }
   }
@@ -41,7 +51,16 @@ void MenuSystem::openPauseMenu() {
   game_.pauseGameplay();
 
   std::vector<MenuBuilder> builders = {
-    MenuBuilder().Command("Settings"),
+    MenuBuilder().Command("Settings")
+                 .ActivationFunction([this] (Game&) {
+                   openSubmenu(Menu({
+                     MenuBuilder().Command("Back")
+                                  .ActivationFunction([this] (Game& game) {
+                                    closePauseMenu();
+                                  })
+                                  .SkipSoundEffect()
+                   }));
+                 }),
     MenuBuilder().Command("Resume Game")
                  .ActivationFunction([] (Game& game) {
                    game.getSystem<MenuSystem>().closePauseMenu();
@@ -61,29 +80,32 @@ void MenuSystem::openPauseMenu() {
                                     }));
   }
 
-  menu_ = CreateMenu(builders);
-
-  cursor_ = 0;
+  menus_.emplace_back(builders);
 
   game_.getMixer().pauseSounds();
   game_.getMixer().playSound("../res/sfx/menu_open.wav");
 }
 
 void MenuSystem::closePauseMenu(bool playSfx) {
-  pauseAnimation_.start(125, 0.0);
-  openState_ = OpenState::Animating;
+  if (menus_.size() > 1) {
+    isMenuChanging_ = true;
+    menuChangeAnimation_.start(250, 0.0);
+
+    if (playSfx) {
+      game_.getMixer().playSound("../res/sfx/menu_deselect.wav");
+    }
+  } else {
+    pauseAnimation_.start(125, 0.0);
+    openState_ = OpenState::Animating;
 
-  if (playSfx) {
-    game_.getMixer().playSound("../res/sfx/menu_close.wav");
+    if (playSfx) {
+      game_.getMixer().playSound("../res/sfx/menu_close.wav");
+    }
   }
 }
 
 void MenuSystem::pressedUp() {
-  cursor_--;
-
-  if (cursor_ < 0) {
-    cursor_ = menu_.size() - 1;
-  }
+  menus_.back().moveCursorUp();
 
   cursorBob_ = 0;
   cursorBobDown_ = true;
@@ -92,11 +114,7 @@ void MenuSystem::pressedUp() {
 }
 
 void MenuSystem::pressedDown() {
-  cursor_++;
-
-  if (cursor_ >= menu_.size()) {
-    cursor_ = 0;
-  }
+  menus_.back().moveCursorDown();
 
   cursorBob_ = 0;
   cursorBobDown_ = true;
@@ -105,13 +123,22 @@ void MenuSystem::pressedDown() {
 }
 
 void MenuSystem::activateOption() {
-  if (menu_[cursor_].type == MenuType::Command) {
-    if (menu_[cursor_].playSfx) {
+  Menu& curMenu = menus_.back();
+  const MenuItem& menuItem = curMenu.getItems()[curMenu.getCursorPosition()];
+
+  if (menuItem.type == MenuType::Command) {
+    if (menuItem.playSfx) {
       game_.getMixer().playSound("../res/sfx/menu_activate.wav");
     }
 
-    if (menu_[cursor_].activationFunction) {
-      menu_[cursor_].activationFunction(game_);
+    if (menuItem.activationFunction) {
+      menuItem.activationFunction(game_);
     }
   }
 }
+
+void MenuSystem::openSubmenu(Menu submenu) {
+  menus_.push_back(std::move(submenu));
+  isMenuChanging_ = true;
+  menuChangeAnimation_.start(250, 1.0);
+}
diff --git a/src/menu_system.h b/src/menu_system.h
index 736d555..c921d0b 100644
--- a/src/menu_system.h
+++ b/src/menu_system.h
@@ -1,6 +1,7 @@
 #ifndef MENU_SYSTEM_H_205861EC
 #define MENU_SYSTEM_H_205861EC
 
+#include <deque>
 #include <vector>
 #include "interpolation.h"
 #include "menu.h"
@@ -34,16 +35,24 @@ public:
 
   double getPauseAnimationProgress() const { return pauseAnimation_.getProgress(); }
 
-  bool isMenuOpen() const { return openState_ == OpenState::Open; }
+  bool isMenuOpen() const { return openState_ == OpenState::Open && !isMenuChanging_; }
 
-  const std::vector<MenuItem>& getMenu() const { return menu_; }
+  // Only call this if a menu is open.
+  Menu& getMenu() { return menus_.back(); }
 
-  int getCursorPosition() const { return cursor_; }
+  // Only call this if a submenu is open.
+  Menu& getParentMenu() { return menus_[menus_.size()-2]; }
 
   int getCursorBob() const { return cursorBob_; }
 
+  double getMenuChangeAnimationProgress() const { return menuChangeAnimation_.getProgress(); }
+
+  bool isMenuChanging() const { return isMenuChanging_; }
+
 private:
 
+  void openSubmenu(Menu submenu);
+
   enum class OpenState {
     Closed,
     Animating,
@@ -51,13 +60,18 @@ private:
   };
 
   Game& game_;
+
   Interpolation pauseAnimation_;
   OpenState openState_ = OpenState::Closed;
-  std::vector<MenuItem> menu_;
-  int cursor_ = 0;
+
+  std::deque<Menu> menus_;
+
   int cursorBob_ = 0;
   bool cursorBobDown_ = true;
   Timer cursorBobTimer_ { 125 };
+
+  Interpolation menuChangeAnimation_;
+  bool isMenuChanging_ = false;
 };
 
 #endif /* end of include guard: MENU_SYSTEM_H_205861EC */
diff --git a/src/renderer.cpp b/src/renderer.cpp
index fc16e20..f7644ca 100644
--- a/src/renderer.cpp
+++ b/src/renderer.cpp
@@ -3,6 +3,7 @@
 #include "consts.h"
 #include "game.h"
 #include "map.h"
+#include "menu.h"
 #include "transform_system.h"
 #include "camera_system.h"
 #include "message_system.h"
@@ -142,11 +143,22 @@ void Renderer::render(Game& game) {
   }
 
   if (menus.getPauseAnimationProgress() > 0.0) {
-    renderMenu(game);
+    renderMenu(game, menus.getMenu());
 
     SDL_SetRenderTarget(ren_.get(), canvas.get());
     if (menus.getPauseAnimationProgress() == 1.0) {
-      SDL_RenderCopy(ren_.get(), menuTex_.get(), nullptr, nullptr);
+      if (menus.isMenuChanging()) {
+        SDL_Rect dest { static_cast<int>(CANVAS_WIDTH * (1.0 - menus.getMenuChangeAnimationProgress())), 0, CANVAS_WIDTH, CANVAS_HEIGHT };
+        SDL_RenderCopy(ren_.get(), menuTex_.get(), nullptr, &dest);
+
+        renderMenu(game, menus.getParentMenu());
+
+        dest.x -= CANVAS_WIDTH;
+        SDL_SetRenderTarget(ren_.get(), canvas.get());
+        SDL_RenderCopy(ren_.get(), menuTex_.get(), nullptr, &dest);
+      } else {
+        SDL_RenderCopy(ren_.get(), menuTex_.get(), nullptr, nullptr);
+      }
     } else {
       int barHeight = CANVAS_HEIGHT / 2 * menus.getPauseAnimationProgress();
       SDL_Rect topHalf { 0, 0, CANVAS_WIDTH, barHeight };
@@ -536,7 +548,7 @@ void Renderer::renderGame(Game& game) {
   }
 }
 
-void Renderer::renderMenu(Game& game) {
+void Renderer::renderMenu(Game& game, const Menu& menu) {
   auto& menus = game.getSystem<MenuSystem>();
 
   if (!menuTex_) {
@@ -558,11 +570,11 @@ void Renderer::renderMenu(Game& game) {
   SDL_RenderFillRect(ren_.get(), nullptr);
 
   const int lineHeight = 16;
-  int totalHeight = menus.getMenu().size() * lineHeight;
+  int totalHeight = menu.getItems().size() * lineHeight;
   std::vector<vec2i> positions;
 
   int index = 0;
-  for (const MenuItem& menuItem : menus.getMenu()) {
+  for (const MenuItem& menuItem : menu.getItems()) {
     switch (menuItem.type) {
       case MenuType::Command: {
         MessageCache output;
@@ -589,8 +601,8 @@ void Renderer::renderMenu(Game& game) {
 
   int cursorTexId = loadImageFromFile("../res/feather_pen.png");
   const SDL_Rect cursorDest {
-    positions[menus.getCursorPosition()].x() - 2 - 16 - menus.getCursorBob(),
-    positions[menus.getCursorPosition()].y() - 8 - menus.getCursorBob(),
+    positions[menu.getCursorPosition()].x() - 2 - 16 - menus.getCursorBob(),
+    positions[menu.getCursorPosition()].y() - 8 - menus.getCursorBob(),
     16,
     16 };
   SDL_RenderCopy(ren_.get(), textures_.at(cursorTexId).get(), nullptr, &cursorDest);
diff --git a/src/renderer.h b/src/renderer.h
index 75bd9fe..82d34a5 100644
--- a/src/renderer.h
+++ b/src/renderer.h
@@ -12,6 +12,7 @@
 class Game;
 class Map;
 class Sprite;
+class Menu;
 
 class sdl_error : public std::logic_error {
 public:
@@ -135,7 +136,7 @@ private:
   std::map<std::string, int> filenameToTexId_;
 
   // Menu rendering
-  void renderMenu(Game& game);
+  void renderMenu(Game& game, const Menu& menu);
 
   texture_ptr menuTex_;
 
-- 
cgit 1.4.1