Index: sdl-game.cpp
===================================================================
--- sdl-game.cpp	(revision 429ac0185601303eb793e2812f3587e8ca05d8ab)
+++ sdl-game.cpp	(revision 40eb0929ada38fe4394564059c7423389b995acd)
@@ -4,7 +4,4 @@
 #include <iostream>
 #include <set>
-#include <stdexcept>
-
-#include <SDL2/SDL_vulkan.h>
 
 #include "IMGUI/imgui_impl_sdl.h"
@@ -54,4 +51,7 @@
 
    shouldRecreateSwapChain = false;
+
+   score = 0;
+   fps = 0.0f;
 }
 
@@ -60,8 +60,11 @@
 
 void VulkanGame::run(int width, int height, unsigned char guiFlags) {
+   // TODO: Maybe call the init code in the constructor instead of in run()
+   // Research this
    cout << "DEBUGGING IS " << (ENABLE_VALIDATION_LAYERS ? "ON" : "OFF") << endl;
 
    cout << "Vulkan Game" << endl;
 
+   // TODO: Move IMGUI initialization in here
    if (initUI(width, height, guiFlags) == RTWO_ERROR) {
       return;
@@ -152,86 +155,13 @@
    }
 
-   done = false;
-   while (!done) {
-
-      gui->processEvents();
-
-      UIEvent uiEvent;
-      while (gui->pollEvent(&uiEvent)) {
-         GameEvent& e = uiEvent.event;
-         SDL_Event sdlEvent = uiEvent.rawEvent.sdl;
-
-         ImGui_ImplSDL2_ProcessEvent(&sdlEvent);
-         if (io.WantCaptureMouse &&
-               (e.type == UI_EVENT_MOUSEBUTTONDOWN || e.type == UI_EVENT_MOUSEBUTTONUP || e.type == UI_EVENT_UNKNOWN)) {
-            if (sdlEvent.type == SDL_MOUSEWHEEL || sdlEvent.type == SDL_MOUSEBUTTONDOWN || sdlEvent.type == SDL_MOUSEBUTTONUP) {
-               continue;
-            }
-         }
-         if (io.WantCaptureKeyboard &&
-               (e.type == UI_EVENT_KEYDOWN || e.type == UI_EVENT_KEYUP)) {
-            if (sdlEvent.type == SDL_KEYDOWN || sdlEvent.type == SDL_KEYUP) {
-               continue;
-            }
-         }
-         if (io.WantTextInput) {
-            // show onscreen keyboard if on mobile
-         }
-
-         if (e.type == UI_EVENT_QUIT) {
-            done = true;
-         }
-      }
-
-      if (shouldRecreateSwapChain) {
-         gui->refreshWindowSize();
-         const bool isMinimized = gui->getWindowWidth() == 0 || gui->getWindowHeight() == 0;
-
-         if (!isMinimized) {
-            // TODO: This should be used if the min image count changes, presumably because a new surface was created
-            // with a different image count or something like that. Maybe I want to add code to query for a new min image count
-            // during swapchain recreation to take advantage of this
-            ImGui_ImplVulkan_SetMinImageCount(swapChainMinImageCount);
-
-            recreateSwapChain();
-
-            imageIndex = 0;
-            shouldRecreateSwapChain = false;
-         }
-      }
-
-      // Start the Dear ImGui frame
-      ImGui_ImplVulkan_NewFrame();
-      ImGui_ImplSDL2_NewFrame(window);
-      ImGui::NewFrame();
-
-      // 2. Show a simple window that we create ourselves. We use a Begin/End pair to created a named window.
-      {
-         static int counter = 0;
-
-         ImGui::Begin("Hello, world!");                          // Create a window called "Hello, world!" and append into it.
-
-         ImGui::Text("This is some useful text.");               // Display some text (you can use a format strings too)
-
-         if (ImGui::Button("Button"))                            // Buttons return true when clicked (most widgets return true when edited/activated)
-            counter++;
-         ImGui::SameLine();
-         ImGui::Text("counter = %d", counter);
-
-         ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate);
-         ImGui::End();
-      }
-
-      ImGui::Render();
-
-      gui->refreshWindowSize();
-      const bool isMinimized = gui->getWindowWidth() == 0 || gui->getWindowHeight() == 0;
-
-      if (!isMinimized) {
-         renderFrame(ImGui::GetDrawData());
-         presentFrame();
-      }
-   }
-
+   currentRenderScreenFn = &VulkanGame::renderMainScreen;
+
+   initGuiValueLists(valueLists);
+
+   valueLists["stats value list"].push_back(UIValue(UIVALUE_INT, "Score", &score));
+   valueLists["stats value list"].push_back(UIValue(UIVALUE_DOUBLE, "FPS", &fps));
+   valueLists["stats value list"].push_back(UIValue(UIVALUE_DOUBLE, "IMGUI FPS", &io.Framerate));
+
+   renderLoop();
    cleanup();
 
@@ -321,4 +251,108 @@
 
    createSyncObjects();
+}
+
+void VulkanGame::renderLoop() {
+   startTime = high_resolution_clock::now();
+   curTime = duration<float, seconds::period>(high_resolution_clock::now() - startTime).count();
+
+   fpsStartTime = curTime;
+   frameCount = 0;
+
+   ImGuiIO& io = ImGui::GetIO();
+
+   done = false;
+   while (!done) {
+
+      curTime = duration<float, seconds::period>(high_resolution_clock::now() - startTime).count();
+
+      if (curTime - fpsStartTime >= 1.0f) {
+         fps = (float)frameCount / (curTime - fpsStartTime);
+
+         frameCount = 0;
+         fpsStartTime = curTime;
+      }
+
+      frameCount++;
+
+      gui->processEvents();
+
+      UIEvent uiEvent;
+      while (gui->pollEvent(&uiEvent)) {
+         GameEvent& e = uiEvent.event;
+         SDL_Event sdlEvent = uiEvent.rawEvent.sdl;
+
+         ImGui_ImplSDL2_ProcessEvent(&sdlEvent);
+         if (io.WantCaptureMouse &&
+            (e.type == UI_EVENT_MOUSEBUTTONDOWN || e.type == UI_EVENT_MOUSEBUTTONUP || e.type == UI_EVENT_UNKNOWN)) {
+            if (sdlEvent.type == SDL_MOUSEWHEEL || sdlEvent.type == SDL_MOUSEBUTTONDOWN || sdlEvent.type == SDL_MOUSEBUTTONUP) {
+               continue;
+            }
+         }
+         if (io.WantCaptureKeyboard &&
+            (e.type == UI_EVENT_KEYDOWN || e.type == UI_EVENT_KEYUP)) {
+            if (sdlEvent.type == SDL_KEYDOWN || sdlEvent.type == SDL_KEYUP) {
+               continue;
+            }
+         }
+         if (io.WantTextInput) {
+            // show onscreen keyboard if on mobile
+         }
+
+         switch (e.type) {
+         case UI_EVENT_MOUSEMOTION:
+            // Currently unused
+            break;
+         case UI_EVENT_WINDOW:
+            // Currently unused
+            break;
+         case UI_EVENT_QUIT:
+            cout << "Quit event detected" << endl;
+            done = true;
+            break;
+         case UI_EVENT_UNHANDLED:
+            cout << "Unhandled event type: 0x" << hex << sdlEvent.type << dec << endl;
+            break;
+         case UI_EVENT_UNKNOWN:
+         default:
+            cout << "Unknown event type: 0x" << hex << sdlEvent.type << dec << endl;
+            break;
+         }
+      }
+
+      if (shouldRecreateSwapChain) {
+         gui->refreshWindowSize();
+         const bool isMinimized = gui->getWindowWidth() == 0 || gui->getWindowHeight() == 0;
+
+         if (!isMinimized) {
+            // TODO: This should be used if the min image count changes, presumably because a new surface was created
+            // with a different image count or something like that. Maybe I want to add code to query for a new min image count
+            // during swapchain recreation to take advantage of this
+            ImGui_ImplVulkan_SetMinImageCount(swapChainMinImageCount);
+
+            recreateSwapChain();
+
+            imageIndex = 0;
+            shouldRecreateSwapChain = false;
+         }
+      }
+
+      // Start the Dear ImGui frame
+      ImGui_ImplVulkan_NewFrame();
+      ImGui_ImplSDL2_NewFrame(window);
+      ImGui::NewFrame();
+
+      (this->*currentRenderScreenFn)();
+
+      ImGui::Render();
+
+      gui->refreshWindowSize();
+      const bool isMinimized = gui->getWindowWidth() == 0 || gui->getWindowHeight() == 0;
+
+      if (!isMinimized) {
+         renderFrame(ImGui::GetDrawData());
+         presentFrame();
+      }
+   }
 }
 
@@ -964,2 +998,127 @@
    vkDestroySwapchainKHR(device, swapChain, nullptr);
 }
+
+void VulkanGame::renderMainScreen() {
+   unsigned int windowWidth = 640;
+   unsigned int windowHeight = 480;
+
+   {
+      int padding = 4;
+      ImGui::SetNextWindowPos(ImVec2(-padding, -padding), ImGuiCond_Once);
+      ImGui::SetNextWindowSize(ImVec2(windowWidth + 2 * padding, windowHeight + 2 * padding), ImGuiCond_Always);
+      ImGui::Begin("WndMain", nullptr,
+         ImGuiWindowFlags_NoTitleBar |
+         ImGuiWindowFlags_NoResize |
+         ImGuiWindowFlags_NoMove);
+
+      ImGui::InvisibleButton("", ImVec2(10, 80));
+      ImGui::InvisibleButton("", ImVec2(285, 18));
+      ImGui::SameLine();
+      if (ImGui::Button("New Game")) {
+         goToScreen(&VulkanGame::renderGameScreen);
+      }
+
+      ImGui::InvisibleButton("", ImVec2(10, 15));
+      ImGui::InvisibleButton("", ImVec2(300, 18));
+      ImGui::SameLine();
+      if (ImGui::Button("Quit")) {
+         quitGame();
+      }
+
+      ImGui::End();
+   }
+}
+
+void VulkanGame::renderGameScreen() {
+   {
+      ImGui::SetNextWindowSize(ImVec2(130, 65), ImGuiCond_Once);
+      ImGui::SetNextWindowPos(ImVec2(10, 50), ImGuiCond_Once);
+      ImGui::Begin("WndStats", nullptr,
+         ImGuiWindowFlags_NoTitleBar |
+         ImGuiWindowFlags_NoResize |
+         ImGuiWindowFlags_NoMove);
+
+      //ImGui::Text(ImGui::GetIO().Framerate);
+      renderGuiValueList(valueLists["stats value list"]);
+
+      ImGui::End();
+   }
+
+   {
+      ImGui::SetNextWindowSize(ImVec2(250, 35), ImGuiCond_Once);
+      ImGui::SetNextWindowPos(ImVec2(380, 10), ImGuiCond_Once);
+      ImGui::Begin("WndMenubar", nullptr,
+         ImGuiWindowFlags_NoTitleBar |
+         ImGuiWindowFlags_NoResize |
+         ImGuiWindowFlags_NoMove);
+      ImGui::InvisibleButton("", ImVec2(155, 18));
+      ImGui::SameLine();
+      if (ImGui::Button("Main Menu")) {
+         goToScreen(&VulkanGame::renderMainScreen);
+      }
+      ImGui::End();
+   }
+
+   {
+      ImGui::SetNextWindowSize(ImVec2(200, 200), ImGuiCond_Once);
+      ImGui::SetNextWindowPos(ImVec2(430, 60), ImGuiCond_Once);
+      ImGui::Begin("WndDebug", nullptr,
+         ImGuiWindowFlags_NoTitleBar |
+         ImGuiWindowFlags_NoResize |
+         ImGuiWindowFlags_NoMove);
+
+      renderGuiValueList(valueLists["debug value list"]);
+
+      ImGui::End();
+   }
+}
+
+void VulkanGame::initGuiValueLists(map<string, vector<UIValue>>& valueLists) {
+   valueLists["stats value list"] = vector<UIValue>();
+   valueLists["debug value list"] = vector<UIValue>();
+}
+
+void VulkanGame::renderGuiValueList(vector<UIValue>& values) {
+   float maxWidth = 0.0f;
+   float cursorStartPos = ImGui::GetCursorPosX();
+
+   for (vector<UIValue>::iterator it = values.begin(); it != values.end(); it++) {
+      float textWidth = ImGui::CalcTextSize(it->label.c_str()).x;
+
+      if (maxWidth < textWidth)
+         maxWidth = textWidth;
+   }
+
+   stringstream ss;
+
+   // TODO: Possibly implement this based on gui/ui-value.hpp instead and use templates
+   // to keep track of the type. This should make it a bit easier to use and maintain
+   // Also, implement this in a way that's agnostic to the UI renderer.
+   for (vector<UIValue>::iterator it = values.begin(); it != values.end(); it++) {
+      ss.str("");
+      ss.clear();
+
+      switch (it->type) {
+      case UIVALUE_INT:
+         ss << it->label << ": " << *(unsigned int*)it->value;
+         break;
+      case UIVALUE_DOUBLE:
+         ss << it->label << ": " << *(double*)it->value;
+         break;
+      }
+
+      float textWidth = ImGui::CalcTextSize(it->label.c_str()).x;
+
+      ImGui::SetCursorPosX(cursorStartPos + maxWidth - textWidth);
+      //ImGui::Text("%s", ss.str().c_str());
+      ImGui::Text("%s: %.1f", it->label.c_str(), *(float*)it->value);
+   }
+}
+
+void VulkanGame::goToScreen(void (VulkanGame::* renderScreenFn)()) {
+   currentRenderScreenFn = renderScreenFn;
+}
+
+void VulkanGame::quitGame() {
+   done = true;
+}
Index: sdl-game.hpp
===================================================================
--- sdl-game.hpp	(revision 429ac0185601303eb793e2812f3587e8ca05d8ab)
+++ sdl-game.hpp	(revision 40eb0929ada38fe4394564059c7423389b995acd)
@@ -2,5 +2,8 @@
 #define _SDL_GAME_H
 
+#include <chrono>
+#include <map>
 #include <vector>
+
 #include <vulkan/vulkan.h>
 
@@ -15,4 +18,5 @@
 
 using namespace std;
+using namespace std::chrono;
 
 #define VulkanGame NewVulkanGame
@@ -23,4 +27,19 @@
    const bool ENABLE_VALIDATION_LAYERS = true;
 #endif
+
+// TODO: Maybe move this to a different header
+
+enum UIValueType {
+   UIVALUE_INT,
+   UIVALUE_DOUBLE,
+};
+
+struct UIValue {
+   UIValueType type;
+   string label;
+   void* value;
+
+   UIValue(UIValueType _type, string _label, void* _value) : type(_type), label(_label), value(_value) {}
+};
 
 class VulkanGame {
@@ -88,9 +107,26 @@
       bool shouldRecreateSwapChain;
 
-      // My code, but not complete. Skips creating the SDL renderer, probably because it doesn't use hardware acceleration.
-      // I should try to get uncapped framerate and compare performance w/ and w/out an SDL renderer
+      /*** High-level vars ***/
+
+      void (VulkanGame::* currentRenderScreenFn)();
+
+      map<string, vector<UIValue>> valueLists;
+
+      int score;
+      float fps;
+
+      // TODO: Make a separate singleton Timer class
+      // It could also deal with the steady_clock vs high_resolution_clock issue
+      time_point<steady_clock> startTime;
+      float fpsStartTime, curTime;
+
+      int frameCount;
+
+      /*** Functions ***/
+
       bool initUI(int width, int height, unsigned char guiFlags);
-      void initVulkan(); // Mostly example code
-      void cleanup(); // Mostly example
+      void initVulkan();
+      void renderLoop();
+      void cleanup();
 
       void createVulkanInstance(const vector<const char*>& validationLayers);
@@ -120,7 +156,17 @@
       void cleanupSwapChain();
 
+      /*** High-level functions ***/
+
+      void renderMainScreen();
+      void renderGameScreen();
+
+      void initGuiValueLists(map<string, vector<UIValue>>& valueLists);
+      void renderGuiValueList(vector<UIValue>& values);
+
+      void goToScreen(void (VulkanGame::* renderScreenFn)());
+      void quitGame();
+
       // Pipeline variables. Hopefully, I can eventually use the GraphicsPipeline_Vulkan class for the imgui pipeline
       VkDescriptorPool descriptorPool;
-
 };
 
