From d3dd81a6a5fa47108af1edd5d421d1c0a96ad6fc Mon Sep 17 00:00:00 2001 From: "C. J. Howard" Date: Wed, 18 Oct 2017 21:17:13 +0800 Subject: [PATCH] Make options menu functional --- src/application.cpp | 486 +++++++++++++++++++++++++++---------- src/application.hpp | 34 ++- src/states/game-state.cpp | 39 ++- src/states/title-state.cpp | 28 ++- src/ui/menu.cpp | 131 ++++++++-- src/ui/menu.hpp | 39 ++- 6 files changed, 587 insertions(+), 170 deletions(-) diff --git a/src/application.cpp b/src/application.cpp index 6e4c612..fbce7c2 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -43,6 +43,8 @@ #define OPENGL_VERSION_MAJOR 3 #define OPENGL_VERSION_MINOR 3 +#undef max + Application::Application(int argc, char* argv[]): state(nullptr), nextState(nullptr), @@ -110,10 +112,6 @@ Application::Application(int argc, char* argv[]): // Get values of required settings settings.get("fullscreen", &fullscreen); - settings.get("fullscreen_width", &fullscreenWidth); - settings.get("fullscreen_height", &fullscreenHeight); - settings.get("windowed_width", &windowedWidth); - settings.get("windowed_height", &windowedHeight); settings.get("swap_interval", &swapInterval); // Load strings @@ -141,61 +139,83 @@ Application::Application(int argc, char* argv[]): SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16); SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); - - // Check desktop display mode - SDL_DisplayMode displayMode; - if (SDL_GetDesktopDisplayMode(0, &displayMode) != 0) - { - std::cerr << "Failed to get desktop display mode: \"" << SDL_GetError() << "\"" << std::endl; - close(EXIT_FAILURE); - return; - } - /* - // Check (usable?) display bounds - SDL_Rect displayBounds; - if (SDL_GetDisplayBounds(0, &displayBounds) != 0) + // Get all possible display modes for the default display + int displayModeCount = SDL_GetNumDisplayModes(0); + for (int i = displayModeCount - 1; i >= 0; --i) { - std::cerr << "Failed to get display bounds: \"" << SDL_GetError() << "\"" << std::endl; - close(EXIT_FAILURE); - return; + SDL_DisplayMode displayMode; + + if (SDL_GetDisplayMode(0, i, &displayMode) != 0) + { + std::cerr << "Failed to get display mode: \"" << SDL_GetError() << "\"" << std::endl; + close(EXIT_FAILURE); + return; + } + + resolutions.push_back(Vector2(displayMode.w, displayMode.h)); } - */ - /* - SDL_DisplayMode displayMode; - if (SDL_GetCurrentDisplayMode(0, &displayMode) != 0) + // Read requested windowed and fullscreen resolutions from settings + Vector2 requestedWindowedResolution; + Vector2 requestedFullscreenResolution; + settings.get("windowed_width", &requestedWindowedResolution.x); + settings.get("windowed_height", &requestedWindowedResolution.y); + settings.get("fullscreen_width", &requestedFullscreenResolution.x); + settings.get("fullscreen_height", &requestedFullscreenResolution.y); + + // Determine desktop resolution + SDL_DisplayMode desktopDisplayMode; + if (SDL_GetDesktopDisplayMode(0, &desktopDisplayMode) != 0) { - std::cerr << "Failed to get display mode: \"" << SDL_GetError() << "\"" << std::endl; + std::cerr << "Failed to get desktop display mode: \"" << SDL_GetError() << "\"" << std::endl; close(EXIT_FAILURE); return; } - */ - - // Use display resolution if settings request - if (windowedWidth == -1 || windowedHeight == -1) - { - windowedWidth = displayMode.w; - windowedHeight = displayMode.h; - } - if (fullscreenWidth == -1 || fullscreenHeight == -1) + Vector2 desktopResolution; + desktopResolution.x = static_cast(desktopDisplayMode.w); + desktopResolution.y = static_cast(desktopDisplayMode.h); + + // Replace requested resolutions of -1 with native resolution + requestedWindowedResolution.x = (requestedWindowedResolution.x == -1.0f) ? desktopResolution.x : requestedWindowedResolution.x; + requestedWindowedResolution.y = (requestedWindowedResolution.y == -1.0f) ? desktopResolution.y : requestedWindowedResolution.y; + requestedFullscreenResolution.x = (requestedFullscreenResolution.x == -1.0f) ? desktopResolution.x : requestedFullscreenResolution.x; + requestedFullscreenResolution.y = (requestedFullscreenResolution.y == -1.0f) ? desktopResolution.y : requestedFullscreenResolution.y; + + // Find indices of closest resolutions to requested windowed and fullscreen resolutions + windowedResolutionIndex = 0; + fullscreenResolutionIndex = 0; + float minWindowedResolutionDistance = std::numeric_limits::max(); + float minFullscreenResolutionDistance = std::numeric_limits::max(); + for (std::size_t i = 0; i < resolutions.size(); ++i) { - fullscreenWidth = displayMode.w; - fullscreenHeight = displayMode.h; + Vector2 windowedResolutionDifference = resolutions[i] - requestedWindowedResolution; + float windowedResolutionDistance = glm::dot(windowedResolutionDifference, windowedResolutionDifference); + if (windowedResolutionDistance <= minWindowedResolutionDistance) + { + minWindowedResolutionDistance = windowedResolutionDistance; + windowedResolutionIndex = i; + } + + Vector2 fullscreenResolutionDifference = resolutions[i] - requestedFullscreenResolution; + float fullscreenResolutionDistance = glm::dot(fullscreenResolutionDifference, fullscreenResolutionDifference); + if (fullscreenResolutionDistance <= minFullscreenResolutionDistance) + { + minFullscreenResolutionDistance = fullscreenResolutionDistance; + fullscreenResolutionIndex = i; + } } - // Determine window parameters + // Determine window parameters and current resolution Uint32 windowFlags = SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL | SDL_WINDOW_ALLOW_HIGHDPI; if (fullscreen) { - width = fullscreenWidth; - height = fullscreenHeight; + resolution = resolutions[fullscreenResolutionIndex]; windowFlags |= SDL_WINDOW_FULLSCREEN; } else { - width = windowedWidth; - height = windowedHeight; + resolution = resolutions[windowedResolutionIndex]; } // Get window title string @@ -203,10 +223,10 @@ Application::Application(int argc, char* argv[]): strings.get("title", &title); // Create window - std::cout << "Creating a " << width << "x" << height; + std::cout << "Creating a " << resolution.x << "x" << resolution.y; std::cout << ((fullscreen) ? " fullscreen" : " windowed"); std::cout << " window... "; - window = SDL_CreateWindow(title.c_str(), SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, windowFlags); + window = SDL_CreateWindow(title.c_str(), SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, static_cast(resolution.x), static_cast(resolution.y), windowFlags); if (window == nullptr) { std::cout << "failed: \"" << SDL_GetError() << "\"" << std::endl; @@ -218,19 +238,6 @@ Application::Application(int argc, char* argv[]): std::cout << "success" << std::endl; } - // Get actual window size - SDL_GetWindowSize(window, &width, &height); - if (fullscreen) - { - fullscreenWidth = width; - fullscreenHeight = height; - } - else - { - windowedWidth = width; - windowedHeight = height; - } - // Print video driver const char* videoDriver = SDL_GetCurrentVideoDriver(); if (!videoDriver) @@ -428,6 +435,10 @@ int Application::execute() // Reset frame timer to counteract frames eaten by state exit() and enter() functions frameTimer.reset(); } + else + { + break; + } } // Update input @@ -507,10 +518,9 @@ void Application::changeFullscreen() if (fullscreen) { - width = fullscreenWidth; - height = fullscreenHeight; + resolution = resolutions[fullscreenResolutionIndex]; - SDL_SetWindowSize(window, width, height); + SDL_SetWindowSize(window, static_cast(resolution.x), static_cast(resolution.y)); if (SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN) != 0) { std::cerr << "Failed to set fullscreen mode: \"" << SDL_GetError() << "\"" << std::endl; @@ -519,9 +529,8 @@ void Application::changeFullscreen() } else { - width = windowedWidth; - height = windowedHeight; - + resolution = resolutions[windowedResolutionIndex]; + if (SDL_SetWindowFullscreen(window, 0) != 0) { std::cerr << "Failed to set windowed mode: \"" << SDL_GetError() << "\"" << std::endl; @@ -529,22 +538,19 @@ void Application::changeFullscreen() } else { - SDL_SetWindowSize(window, width, height); + SDL_SetWindowSize(window, static_cast(resolution.x), static_cast(resolution.y)); SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); } } - // Get actual window size - SDL_GetWindowSize(window, &width, &height); - // Print mode and resolution if (fullscreen) { - std::cout << "Changed to fullscreen mode at resolution " << width << "x" << height << std::endl; + std::cout << "Changed to fullscreen mode at resolution " << resolution.x << "x" << resolution.y << std::endl; } else { - std::cout << "Changed to windowed mode at resolution " << width << "x" << height << std::endl; + std::cout << "Changed to windowed mode at resolution " << resolution.x << "x" << resolution.y << std::endl; } // Save settings @@ -765,6 +771,12 @@ bool Application::loadUI() std::cerr << "Failed to load copyright font" << std::endl; } + levelNameFont = new Font(512, 512); + if (!fontLoader->load("data/fonts/Vollkorn-Regular.ttf", static_cast(fontSizePX * 2.0f + 0.5f), levelNameFont)) + { + std::cerr << "Failed to load level name font" << std::endl; + } + delete fontLoader; // Load UI textures @@ -848,13 +860,13 @@ bool Application::loadUI() // Setup root UI element uiRootElement = new UIContainer(); - uiRootElement->setDimensions(Vector2(width, height)); + uiRootElement->setDimensions(resolution); mouse->addMouseMotionObserver(uiRootElement); mouse->addMouseButtonObserver(uiRootElement); // Create blackout element (for screen transitions) blackoutImage = new UIImage(); - blackoutImage->setDimensions(Vector2(width, height)); + blackoutImage->setDimensions(resolution); blackoutImage->setLayerOffset(ANTKEEPER_UI_LAYER_BLACKOUT); blackoutImage->setTintColor(Vector4(0.0f, 0.0f, 0.0f, 1.0f)); blackoutImage->setVisible(false); @@ -862,7 +874,7 @@ bool Application::loadUI() // Create darken element (for darkening title screen) darkenImage = new UIImage(); - darkenImage->setDimensions(Vector2(width, height)); + darkenImage->setDimensions(resolution); darkenImage->setLayerOffset(ANTKEEPER_UI_LAYER_DARKEN); darkenImage->setTintColor(Vector4(0.0f, 0.0f, 0.0f, 0.35f)); darkenImage->setVisible(false); @@ -870,7 +882,7 @@ bool Application::loadUI() // Create splash screen background element splashBackgroundImage = new UIImage(); - splashBackgroundImage->setDimensions(Vector2(width, height)); + splashBackgroundImage->setDimensions(resolution); splashBackgroundImage->setLayerOffset(-1); splashBackgroundImage->setTintColor(Vector4(0.0f, 0.0f, 0.0f, 1.0f)); splashBackgroundImage->setVisible(false); @@ -888,7 +900,7 @@ bool Application::loadUI() titleImage = new UIImage(); titleImage->setAnchor(Vector2(0.5f, 0.0f)); titleImage->setDimensions(Vector2(titleTexture->getWidth(), titleTexture->getHeight())); - titleImage->setTranslation(Vector2(0.0f, (int)(height * (1.0f / 4.0f) - titleTexture->getHeight() * 0.5f))); + titleImage->setTranslation(Vector2(0.0f, (int)(resolution.y * (1.0f / 4.0f) - titleTexture->getHeight() * 0.5f))); titleImage->setTexture(titleTexture); titleImage->setVisible(false); titleImage->setLayerOffset(ANTKEEPER_UI_LAYER_MENU); @@ -912,7 +924,7 @@ bool Application::loadUI() anyKeyLabel = new UILabel(); anyKeyLabel->setAnchor(Vector2(0.5f, 1.0f)); anyKeyLabel->setFont(menuFont); - anyKeyLabel->setTranslation(Vector2(0.0f, (int)(-height * (1.0f / 4.0f) - menuFont->getMetrics().getHeight() * 0.5f))); + anyKeyLabel->setTranslation(Vector2(0.0f, (int)(-resolution.y * (1.0f / 4.0f) - menuFont->getMetrics().getHeight() * 0.5f))); anyKeyLabel->setText(pressAnyKeyString); anyKeyLabel->setVisible(false); uiRootElement->addChild(anyKeyLabel); @@ -949,22 +961,12 @@ bool Application::loadUI() depthTextureImage->setVisible(false); uiRootElement->addChild(depthTextureImage); - // Create level ID - levelIDLabel = new UILabel(); - levelIDLabel->setAnchor(Vector2(0.5f, 0.0f)); - levelIDLabel->setFont(menuFont); - levelIDLabel->setTranslation(Vector2(0.0f, (int)(height * (1.0f / 4.0f) - menuFont->getMetrics().getHeight() * 1.5f))); - levelIDLabel->setText(""); - levelIDLabel->setVisible(false); - uiRootElement->addChild(levelIDLabel); - // Create level name label levelNameLabel = new UILabel(); - levelNameLabel->setAnchor(Vector2(0.5f, 0.0f)); - levelNameLabel->setFont(menuFont); - levelNameLabel->setTranslation(Vector2(0.0f, (int)(height * (1.0f / 4.0f) - menuFont->getMetrics().getHeight() * 0.5f))); - levelNameLabel->setText(""); + levelNameLabel->setAnchor(Vector2(0.5f, 0.5f)); + levelNameLabel->setFont(levelNameFont); levelNameLabel->setVisible(false); + levelNameLabel->setLayerOffset(ANTKEEPER_UI_LAYER_HUD); uiRootElement->addChild(levelNameLabel); // Create toolbar @@ -979,7 +981,7 @@ bool Application::loadUI() toolbar->addButton(toolForcepsTexture, std::bind(&std::printf, "2\n"), std::bind(&std::printf, "2\n")); toolbar->addButton(toolTrowelTexture, std::bind(&std::printf, "3\n"), std::bind(&std::printf, "3\n")); toolbar->resize(); - uiRootElement->addChild(toolbar->getContainer()); + //uiRootElement->addChild(toolbar->getContainer()); toolbar->getContainer()->setVisible(false); toolbar->getContainer()->setActive(false); @@ -1046,7 +1048,7 @@ bool Application::loadUI() Vector4 menuFadeOutDeltaColor = Vector4(0.0f, 0.0f, 0.0f, -1.0f); float menuSlideInDuration = 0.35f; Vector2 menuSlideInStartTranslation = Vector2(-64.0f, 0.0f); - Vector2 menuSlideInDeltaTranslation = Vector2((int)(64.0f + width / 8.0f), 0.0f); + Vector2 menuSlideInDeltaTranslation = Vector2((int)(64.0f + resolution.x / 8.0f), 0.0f); // Setup main menu tween menuFadeInTween = new Tween(EaseFunction::OUT_QUINT, 0.0f, menuFadeInDuration, menuFadeInStartColor, menuFadeInDeltaColor); @@ -1083,27 +1085,27 @@ bool Application::loadUI() MenuItem* continueItem = mainMenu->addItem(); continueItem->setActivatedCallback(std::bind(&Application::continueGame, this)); - continueItem->setLabel(continueString); - - MenuItem* newGameItem = mainMenu->addItem(); - newGameItem->setActivatedCallback(std::bind(&Application::newGame, this)); - newGameItem->setLabel(newGameString); + continueItem->setName(continueString); MenuItem* levelsItem = mainMenu->addItem(); levelsItem->setActivatedCallback(std::bind(&Application::openMenu, this, levelsMenu)); - levelsItem->setLabel(levelsString); + levelsItem->setName(levelsString); + + MenuItem* newGameItem = mainMenu->addItem(); + newGameItem->setActivatedCallback(std::bind(&Application::newGame, this)); + newGameItem->setName(newGameString); MenuItem* sandboxItem = mainMenu->addItem(); sandboxItem->setActivatedCallback(std::bind(&std::printf, "1\n")); - sandboxItem->setLabel(sandboxString); + sandboxItem->setName(sandboxString); MenuItem* optionsItem = mainMenu->addItem(); optionsItem->setActivatedCallback(std::bind(&Application::openMenu, this, optionsMenu)); - optionsItem->setLabel(optionsString); + optionsItem->setName(optionsString); MenuItem* exitItem = mainMenu->addItem(); exitItem->setActivatedCallback(std::bind(&Application::close, this, EXIT_SUCCESS)); - exitItem->setLabel(exitString); + exitItem->setName(exitString); mainMenu->getUIContainer()->setActive(false); mainMenu->getUIContainer()->setVisible(false); @@ -1138,13 +1140,23 @@ bool Application::loadUI() ( [this, world, level]() { - closeMenu(); loadWorld(world); loadLevel(level); - changeState(gameState); + + // Close levels menu + closeMenu(); + + // Begin title fade-out + titleFadeOutTween->reset(); + titleFadeOutTween->start(); + + // Begin fade-out + fadeOutTween->setEndCallback(std::bind(&Application::changeState, this, gameState)); + fadeOutTween->reset(); + fadeOutTween->start(); } ); - levelItem->setLabel(label); + levelItem->setName(label); } } @@ -1156,7 +1168,7 @@ bool Application::loadUI() openMenu(previousActiveMenu); } ); - backItem->setLabel(backString); + backItem->setName(backString); levelsMenu->getUIContainer()->setActive(false); levelsMenu->getUIContainer()->setVisible(false); @@ -1169,12 +1181,51 @@ bool Application::loadUI() optionsMenu->getUIContainer()->setAnchor(Vector2(0.5f, 0.8f)); optionsMenu->getUIContainer()->setLayerOffset(ANTKEEPER_UI_LAYER_MENU); optionsMenu->setLineSpacing(1.0f); + optionsMenu->setColumnMargin(menuFont->getWidth("MM")); - MenuItem* resolutionItem = optionsMenu->addItem(); - resolutionItem->setLabel("Resolution"); + MenuItem* windowedResolutionItem = optionsMenu->addItem(); + windowedResolutionItem->setName("Windowed Resolution"); + MenuItem* fullscreenResolutionItem = optionsMenu->addItem(); + fullscreenResolutionItem->setName("Fullscreen Resolution"); + for (const Vector2& resolution: resolutions) + { + std::stringstream stream; + stream << resolution.x << "x" << resolution.y; + + windowedResolutionItem->addValue(stream.str()); + fullscreenResolutionItem->addValue(stream.str()); + } + windowedResolutionItem->setValueIndex(windowedResolutionIndex); + windowedResolutionItem->setActivatedCallback(std::bind(&Application::incrementMenuItem, this)); + windowedResolutionItem->setValueChangedCallback(std::bind(&Application::selectWindowedResolution, this, std::placeholders::_1)); + fullscreenResolutionItem->setValueIndex(fullscreenResolutionIndex); + fullscreenResolutionItem->setActivatedCallback(std::bind(&Application::incrementMenuItem, this)); + fullscreenResolutionItem->setValueChangedCallback(std::bind(&Application::selectFullscreenResolution, this, std::placeholders::_1)); MenuItem* fullscreenItem = optionsMenu->addItem(); - fullscreenItem->setLabel("Fullscreen"); + fullscreenItem->setName("Fullscreen"); + fullscreenItem->addValue("Off"); + fullscreenItem->addValue("On"); + fullscreenItem->setValueIndex((fullscreen == 0) ? 0 : 1); + fullscreenItem->setActivatedCallback(std::bind(&Application::incrementMenuItem, this)); + fullscreenItem->setValueChangedCallback(std::bind(&Application::selectFullscreenMode, this, std::placeholders::_1)); + + MenuItem* vsyncItem = optionsMenu->addItem(); + vsyncItem->setName("VSync"); + vsyncItem->addValue("Off"); + vsyncItem->addValue("On"); + vsyncItem->setValueIndex((swapInterval == 0) ? 0 : 1); + vsyncItem->setActivatedCallback(std::bind(&Application::incrementMenuItem, this)); + vsyncItem->setValueChangedCallback(std::bind(&Application::selectVSyncMode, this, std::placeholders::_1)); + + MenuItem* languageItem = optionsMenu->addItem(); + languageItem->setName("Language"); + languageItem->addValue("English"); + languageItem->addValue("Chinese"); + languageItem->setActivatedCallback(std::bind(&Application::incrementMenuItem, this)); + + MenuItem* controlsItem = optionsMenu->addItem(); + controlsItem->setName("Controls"); MenuItem* backItem = optionsMenu->addItem(); backItem->setActivatedCallback @@ -1184,7 +1235,7 @@ bool Application::loadUI() openMenu(previousActiveMenu); } ); - backItem->setLabel(backString); + backItem->setName(backString); optionsMenu->getUIContainer()->setActive(false); optionsMenu->getUIContainer()->setVisible(false); @@ -1200,15 +1251,15 @@ bool Application::loadUI() MenuItem* resumeItem = pauseMenu->addItem(); resumeItem->setActivatedCallback(std::bind(&Application::unpauseSimulation, this)); - resumeItem->setLabel("Resume"); + resumeItem->setName("Resume"); MenuItem* levelsItem = pauseMenu->addItem(); levelsItem->setActivatedCallback(std::bind(&Application::openMenu, this, levelsMenu)); - levelsItem->setLabel(levelsString); + levelsItem->setName(levelsString); MenuItem* optionsItem = pauseMenu->addItem(); optionsItem->setActivatedCallback(std::bind(&Application::openMenu, this, optionsMenu)); - optionsItem->setLabel(optionsString); + optionsItem->setName(optionsString); MenuItem* mainMenuItem = pauseMenu->addItem(); mainMenuItem->setActivatedCallback @@ -1224,11 +1275,11 @@ bool Application::loadUI() fadeOutTween->start(); } ); - mainMenuItem->setLabel("Main Menu"); + mainMenuItem->setName("Main Menu"); MenuItem* exitItem = pauseMenu->addItem(); exitItem->setActivatedCallback(std::bind(&Application::close, this, EXIT_SUCCESS)); - exitItem->setLabel(exitString); + exitItem->setName(exitString); pauseMenu->getUIContainer()->setActive(false); pauseMenu->getUIContainer()->setVisible(false); @@ -1254,8 +1305,8 @@ bool Application::loadUI() uiLayer->addObject(uiBatch); uiLayer->addObject(&uiCamera); - defaultRenderTarget.width = width; - defaultRenderTarget.height = height; + defaultRenderTarget.width = static_cast(resolution.x); + defaultRenderTarget.height = static_cast(resolution.y); defaultRenderTarget.framebuffer = 0; resizeUI(); @@ -1371,11 +1422,11 @@ bool Application::loadGame() void Application::resizeUI() { // Adjust UI dimensions - uiRootElement->setDimensions(Vector2(width, height)); + uiRootElement->setDimensions(resolution); uiRootElement->update(); // Adjust UI camera projection - uiCamera.setOrthographic(0.0f, static_cast(width), static_cast(height), 0.0f, -1.0f, 1.0f); + uiCamera.setOrthographic(0.0f, resolution.x, resolution.y, 0.0f, -1.0f, 1.0f); } void Application::openMenu(Menu* menu) @@ -1423,6 +1474,43 @@ void Application::activateMenuItem() } } +void Application::incrementMenuItem() +{ + if (activeMenu != nullptr) + { + MenuItem* item = activeMenu->getSelectedItem(); + if (item != nullptr) + { + if (item->getValueCount() != 0) + { + item->setValueIndex((item->getValueIndex() + 1) % item->getValueCount()); + } + } + } +} + +void Application::decrementMenuItem() +{ + if (activeMenu != nullptr) + { + MenuItem* item = activeMenu->getSelectedItem(); + if (item != nullptr) + { + if (item->getValueCount() != 0) + { + if (!item->getValueIndex()) + { + item->setValueIndex(item->getValueCount() - 1); + } + else + { + item->setValueIndex(item->getValueIndex() - 1); + } + } + } + } +} + void Application::continueGame() { closeMenu(); @@ -1441,8 +1529,15 @@ void Application::continueGame() { loadLevel(level); } + + // Begin title fade-out + titleFadeOutTween->reset(); + titleFadeOutTween->start(); - changeState(gameState); + // Begin fade-out + fadeOutTween->setEndCallback(std::bind(&Application::changeState, this, gameState)); + fadeOutTween->reset(); + fadeOutTween->start(); } void Application::newGame() @@ -1459,11 +1554,6 @@ void Application::newGame() // Select first level of the first world currentWorldIndex = 0; currentLevelIndex = 0; - - // Save continue world and level indices - settings.set("continue_world", currentWorldIndex); - settings.set("continue_level", currentLevelIndex); - saveUserSettings(); // Begin fade-out fadeOutTween->setEndCallback(std::bind(&Application::changeState, this, gameState)); @@ -1583,4 +1673,156 @@ void Application::setDisplayDebugInfo(bool display) frameTimeLabel->setVisible(displayDebugInfo); depthTextureImage->setVisible(displayDebugInfo); -} \ No newline at end of file +} + +std::string Application::getLevelName(std::size_t world, std::size_t level) const +{ + // Form level ID string + char levelIDBuffer[6]; + std::sprintf(levelIDBuffer, "%02d-%02d", static_cast(world + 1), static_cast(level + 1)); + std::string levelID(levelIDBuffer); + + // Look up level name + std::string levelName; + strings.get(levelIDBuffer, &levelName); + + return levelName; +} + +void Application::selectWindowedResolution(std::size_t index) +{ + windowedResolutionIndex = index; + + if (!fullscreen) + { + // Select resolution + resolution = resolutions[windowedResolutionIndex]; + + // Resize window + SDL_SetWindowSize(window, static_cast(resolution.x), static_cast(resolution.y)); + SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); + + // Resize UI + resizeUI(); + + // Notify window observers + inputManager->update(); + } + + // Save settings + settings.set("windowed_width", resolutions[windowedResolutionIndex].x); + settings.set("windowed_height", resolutions[windowedResolutionIndex].y); + saveUserSettings(); +} + +void Application::selectFullscreenResolution(std::size_t index) +{ + fullscreenResolutionIndex = index; + + if (fullscreen) + { + // Select resolution + resolution = resolutions[fullscreenResolutionIndex]; + + // Resize window + SDL_SetWindowSize(window, static_cast(resolution.x), static_cast(resolution.y)); + SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); + + // Resize UI + resizeUI(); + + // Notify window observers + inputManager->update(); + } + + // Save settings + settings.set("fullscreen_width", resolutions[fullscreenResolutionIndex].x); + settings.set("fullscreen_height", resolutions[fullscreenResolutionIndex].y); + saveUserSettings(); +} + +void Application::selectFullscreenMode(std::size_t index) +{ + fullscreen = (index == 1); + + if (fullscreen) + { + resolution = resolutions[fullscreenResolutionIndex]; + + SDL_SetWindowSize(window, static_cast(resolution.x), static_cast(resolution.y)); + if (SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN) != 0) + { + std::cerr << "Failed to set fullscreen mode: \"" << SDL_GetError() << "\"" << std::endl; + fullscreen = false; + } + } + else + { + resolution = resolutions[windowedResolutionIndex]; + + if (SDL_SetWindowFullscreen(window, 0) != 0) + { + std::cerr << "Failed to set windowed mode: \"" << SDL_GetError() << "\"" << std::endl; + fullscreen = true; + } + else + { + SDL_SetWindowSize(window, static_cast(resolution.x), static_cast(resolution.y)); + SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); + } + } + + // Print mode and resolution + if (fullscreen) + { + std::cout << "Changed to fullscreen mode at resolution " << resolution.x << "x" << resolution.y << std::endl; + } + else + { + std::cout << "Changed to windowed mode at resolution " << resolution.x << "x" << resolution.y << std::endl; + } + + // Save settings + settings.set("fullscreen", fullscreen); + saveUserSettings(); + + // Resize UI + resizeUI(); + + // Notify window observers + inputManager->update(); +} + +// index: 0 = off, 1 = on +void Application::selectVSyncMode(std::size_t index) +{ + swapInterval = (index == 0) ? 0 : 1; + + if (swapInterval == 1) + { + std::cout << "Enabling vertical sync... "; + } + else + { + std::cout << "Disabling vertical sync... "; + } + + if (SDL_GL_SetSwapInterval(swapInterval) != 0) + { + std::cout << "failed: \"" << SDL_GetError() << "\"" << std::endl; + swapInterval = SDL_GL_GetSwapInterval(); + } + else + { + std::cout << "success" << std::endl; + } + + // Save settings + settings.set("swap_interval", swapInterval); + saveUserSettings(); +} + +void Application::selectLanguage(std::size_t index) +{ + +} diff --git a/src/application.hpp b/src/application.hpp index 42f5c39..0a5e4d7 100644 --- a/src/application.hpp +++ b/src/application.hpp @@ -92,6 +92,8 @@ public: void closeMenu(); void selectMenuItem(std::size_t index); void activateMenuItem(); + void incrementMenuItem(); + void decrementMenuItem(); void loadWorld(std::size_t index); void loadLevel(std::size_t index); @@ -108,6 +110,15 @@ public: void setDisplayDebugInfo(bool display); + std::string getLevelName(std::size_t world, std::size_t level) const; + + // Options menu functions + void selectWindowedResolution(std::size_t index); + void selectFullscreenResolution(std::size_t index); + void selectFullscreenMode(std::size_t index); + void selectVSyncMode(std::size_t index); + void selectLanguage(std::size_t index); + private: ApplicationState* state; ApplicationState* nextState; @@ -127,16 +138,6 @@ public: // Settings ParameterDict settings; - // Window - bool fullscreen; - int fullscreenWidth; - int fullscreenHeight; - int windowedWidth; - int windowedHeight; - int swapInterval; - int width; - int height; - // State machine LoadingState* loadingState; SplashState* splashState; @@ -234,6 +235,7 @@ public: float fontSizePX; Font* menuFont; Font* copyrightFont; + Font* levelNameFont; // UI textures Texture* splashTexture; @@ -276,7 +278,6 @@ public: UIImage* contextButtonImage0; UIImage* contextButtonImage1; UIImage* depthTextureImage; - UILabel* levelIDLabel; UILabel* levelNameLabel; Toolbar* toolbar; PieMenu* pieMenu; @@ -337,6 +338,17 @@ public: // Debug LineBatcher* lineBatcher; bool displayDebugInfo; + + // Options menu values + bool fullscreen; + int swapInterval; + Vector2 resolution; + std::vector resolutions; + std::size_t windowedResolutionIndex; + std::size_t fullscreenResolutionIndex; + int* fullscreenModes; + int* vsyncModes; + std::string* languages; }; #endif // APPLICATION_HPP \ No newline at end of file diff --git a/src/states/game-state.cpp b/src/states/game-state.cpp index 6fe821d..0719622 100644 --- a/src/states/game-state.cpp +++ b/src/states/game-state.cpp @@ -38,6 +38,19 @@ GameState::~GameState() void GameState::enter() { + int continueWorld = -1; + int continueLevel = -1; + application->settings.get("continue_world", &continueWorld); + application->settings.get("continue_level", &continueLevel); + + if (continueWorld != application->currentWorldIndex || continueLevel != application->currentLevelIndex) + { + // Save continue world and level indices + application->settings.set("continue_world", application->currentWorldIndex); + application->settings.set("continue_level", application->currentLevelIndex); + application->saveUserSettings(); + } + // Setup HUD application->rectangularPaletteImage->setVisible(true); application->rectangularPaletteImage->setActive(true); @@ -97,6 +110,11 @@ void GameState::enter() // Position options menu application->optionsMenu->getUIContainer()->setAnchor(Vector2(0.5f, 0.5f)); + application->levelsMenu->getUIContainer()->setAnchor(Vector2(0.5f, 0.5f)); + + // Show level name + application->levelNameLabel->setText(application->getLevelName(application->currentWorldIndex, application->currentLevelIndex)); + //application->levelNameLabel->setVisible(true); // Begin fade-in application->fadeInTween->start(); @@ -128,9 +146,9 @@ void GameState::execute() { if (selectedItem != nullptr) { - if (selectedItem->getIndex() < application->activeMenu->getItemCount() - 1) + if (selectedItem->getItemIndex() < application->activeMenu->getItemCount() - 1) { - application->selectMenuItem(selectedItem->getIndex() + 1); + application->selectMenuItem(selectedItem->getItemIndex() + 1); } else { @@ -146,9 +164,9 @@ void GameState::execute() { if (selectedItem != nullptr) { - if (selectedItem->getIndex() > 0) + if (selectedItem->getItemIndex() > 0) { - application->selectMenuItem(selectedItem->getIndex() - 1); + application->selectMenuItem(selectedItem->getItemIndex() - 1); } else { @@ -161,6 +179,15 @@ void GameState::execute() } } + if (application->menuLeft.isTriggered() && !application->menuLeft.wasTriggered()) + { + application->decrementMenuItem(); + } + else if (application->menuRight.isTriggered() && !application->menuRight.wasTriggered()) + { + application->incrementMenuItem(); + } + if (application->menuSelect.isTriggered() && !application->menuSelect.wasTriggered()) { application->activateMenuItem(); @@ -223,8 +250,8 @@ void GameState::execute() // Picking glm::ivec2 mousePosition = application->mouse->getCurrentPosition(); - mousePosition.y = application->height - mousePosition.y; - Vector4 viewport(0.0f, 0.0f, application->width, application->height); + mousePosition.y = application->resolution.y - mousePosition.y; + Vector4 viewport(0.0f, 0.0f, application->resolution.x, application->resolution.y); Vector3 mouseNear = application->camera.unproject(Vector3(mousePosition.x, mousePosition.y, 0.0f), viewport); Vector3 mouseFar = application->camera.unproject(Vector3(mousePosition.x, mousePosition.y, 1.0f), viewport); diff --git a/src/states/title-state.cpp b/src/states/title-state.cpp index 56208a0..eda08a7 100644 --- a/src/states/title-state.cpp +++ b/src/states/title-state.cpp @@ -37,7 +37,13 @@ void TitleState::enter() application->backgroundLayer->addObject(&application->bgBatch); application->inputManager->addWindowObserver(this); - windowResized(application->width, application->height); + windowResized(application->resolution.x, application->resolution.y); + + application->camera.setPerspective( + glm::radians(25.0f), + application->resolution.x / application->resolution.y, + 0.1f, + 1000.0f); // Setup camera controller application->surfaceCam->setCamera(&application->camera); @@ -63,6 +69,7 @@ void TitleState::enter() // Position options menu application->optionsMenu->getUIContainer()->setAnchor(Vector2(0.5f, 0.8f)); + application->levelsMenu->getUIContainer()->setAnchor(Vector2(0.5f, 0.8f)); // Setup fade-in application->blackoutImage->setVisible(true); @@ -80,9 +87,9 @@ void TitleState::execute() { if (selectedItem != nullptr) { - if (selectedItem->getIndex() < application->activeMenu->getItemCount() - 1) + if (selectedItem->getItemIndex() < application->activeMenu->getItemCount() - 1) { - application->selectMenuItem(selectedItem->getIndex() + 1); + application->selectMenuItem(selectedItem->getItemIndex() + 1); } else { @@ -98,9 +105,9 @@ void TitleState::execute() { if (selectedItem != nullptr) { - if (selectedItem->getIndex() > 0) + if (selectedItem->getItemIndex() > 0) { - application->selectMenuItem(selectedItem->getIndex() - 1); + application->selectMenuItem(selectedItem->getItemIndex() - 1); } else { @@ -113,6 +120,15 @@ void TitleState::execute() } } + if (application->menuLeft.isTriggered() && !application->menuLeft.wasTriggered()) + { + application->decrementMenuItem(); + } + else if (application->menuRight.isTriggered() && !application->menuRight.wasTriggered()) + { + application->incrementMenuItem(); + } + if (application->menuSelect.isTriggered() && !application->menuSelect.wasTriggered()) { application->activateMenuItem(); @@ -158,6 +174,7 @@ void TitleState::windowClosed() void TitleState::windowResized(int width, int height) { + /* // Update application dimensions application->width = width; application->height = height; @@ -185,4 +202,5 @@ void TitleState::windowResized(int width, int height) (float)application->width / (float)application->height, 0.1f, 1000.0f); + */ } diff --git a/src/ui/menu.cpp b/src/ui/menu.cpp index 327f885..cdb7598 100644 --- a/src/ui/menu.cpp +++ b/src/ui/menu.cpp @@ -27,18 +27,34 @@ MenuItem::MenuItem(Menu* parent, std::size_t index): index(index), selectedCallback(nullptr), deselectedCallback(nullptr), - activatedCallback(nullptr) + activatedCallback(nullptr), + valueChangedCallback(nullptr), + valueIndex(0), + nameLabel(nullptr), + valueLabel(nullptr), + rowContainer(nullptr) + { - label = new UILabel(); + nameLabel = new UILabel(); + nameLabel->setAnchor(Vector2(0.0f, 0.0f)); + + valueLabel = new UILabel(); + valueLabel->setAnchor(Vector2(1.0f, 0.0f)); + + rowContainer = new UIContainer(); + rowContainer->addChild(nameLabel); + rowContainer->addChild(valueLabel); - label->setMouseOverCallback(std::bind(&Menu::select, parent, index)); - label->setMouseMovedCallback(std::bind(&Menu::select, parent, index)); - label->setMousePressedCallback(std::bind(&Menu::activate, parent)); + rowContainer->setMouseOverCallback(std::bind(&Menu::select, parent, index)); + rowContainer->setMouseMovedCallback(std::bind(&Menu::select, parent, index)); + rowContainer->setMousePressedCallback(std::bind(&Menu::activate, parent)); } MenuItem::~MenuItem() { - delete label; + delete nameLabel; + delete valueLabel; + delete rowContainer; } void MenuItem::select() @@ -80,12 +96,46 @@ void MenuItem::setActivatedCallback(std::function callback) this->activatedCallback = callback; } -void MenuItem::setLabel(const std::string& text) +void MenuItem::setValueChangedCallback(std::function callback) +{ + this->valueChangedCallback = callback; +} + +void MenuItem::setName(const std::string& text) +{ + nameLabel->setText(text); + parent->resize(); +} + +void MenuItem::addValue(const std::string& text) { - label->setText(text); + values.push_back(text); + valueLabel->setText(values[valueIndex]); parent->resize(); } +void MenuItem::removeValues() +{ + values.clear(); + valueIndex = 0; + valueLabel->setText(std::string()); + parent->resize(); +} + +void MenuItem::setValueIndex(std::size_t index) +{ + if (valueIndex != index) + { + valueIndex = index; + valueLabel->setText(values[index]); + + if (valueChangedCallback != nullptr) + { + valueChangedCallback(index); + } + } +} + bool MenuItem::isSelected() const { return (parent->getSelectedItem() == this); @@ -96,7 +146,8 @@ Menu::Menu(): enteredCallback(nullptr), exitedCallback(nullptr), font(nullptr), - lineSpacing(1.0f) + lineSpacing(1.0f), + columnMargin(0.0f) { container = new UIContainer(); resize(); @@ -130,12 +181,16 @@ MenuItem* Menu::addItem() MenuItem* item = new MenuItem(this, items.size()); items.push_back(item); - // Set item label font - item->label->setFont(font); - item->label->setTintColor(Vector4(1.0f, 1.0f, 1.0f, 0.35f)); + // Set item fonts + item->nameLabel->setFont(font); + item->valueLabel->setFont(font); - // Add item label to UI container - container->addChild(item->label); + // Set item colors + item->nameLabel->setTintColor(Vector4(1.0f, 1.0f, 1.0f, 0.35f)); + item->valueLabel->setTintColor(Vector4(1.0f, 1.0f, 1.0f, 0.35f)); + + // Add item labels to UI container + container->addChild(item->rowContainer); // Resize UI container resize(); @@ -147,8 +202,8 @@ void Menu::removeItems() { for (MenuItem* item: items) { - // Remove label from UI container - container->removeChild(item->label); + // Remove labels from UI container + container->removeChild(item->rowContainer); delete item; } @@ -172,7 +227,8 @@ void Menu::setFont(Font* font) this->font = font; for (MenuItem* item: items) { - item->label->setFont(font); + item->nameLabel->setFont(font); + item->valueLabel->setFont(font); } resize(); @@ -184,13 +240,20 @@ void Menu::setLineSpacing(float spacing) resize(); } +void Menu::setColumnMargin(float margin) +{ + columnMargin = margin; + resize(); +} + void Menu::deselect() { if (selectedItem != nullptr) { selectedItem->deselect(); - selectedItem->label->setTintColor(Vector4(1.0f, 1.0f, 1.0f, 0.35f)); + selectedItem->nameLabel->setTintColor(Vector4(1.0f, 1.0f, 1.0f, 0.35f)); + selectedItem->valueLabel->setTintColor(Vector4(1.0f, 1.0f, 1.0f, 0.35f)); selectedItem = nullptr; } @@ -204,7 +267,8 @@ void Menu::select(std::size_t index) item->select(); selectedItem = item; - selectedItem->label->setTintColor(Vector4(1.0f, 1.0f, 1.0f, 1.0f)); + selectedItem->nameLabel->setTintColor(Vector4(1.0f, 1.0f, 1.0f, 1.0f)); + selectedItem->valueLabel->setTintColor(Vector4(1.0f, 1.0f, 1.0f, 1.0f)); } void Menu::activate() @@ -223,14 +287,37 @@ void Menu::resize() } else { - Vector2 dimensions(0.0f); + // Determine maximum width of menu + float menuWidth = 0.0f; + + // For each menu item + for (std::size_t i = 0; i < items.size(); ++i) + { + const MenuItem* item = items[i]; + + // Calculate width of item name label + float nameLabelWidth = item->nameLabel->getDimensions().x; + + // For each item value + for (std::size_t j = 0; j < item->getValueCount(); ++j) + { + // Calculate width of item value label + float valueLabelWidth = font->getWidth(item->getValue(j).c_str()); + + menuWidth = std::max(menuWidth, nameLabelWidth + columnMargin + valueLabelWidth); + } + menuWidth = std::max(menuWidth, nameLabelWidth); + } + + Vector2 dimensions(menuWidth, 0.0f); for (std::size_t i = 0; i < items.size(); ++i) { const MenuItem* item = items[i]; - item->label->setTranslation(Vector2(0.0f, static_cast(font->getMetrics().getHeight() * lineSpacing * static_cast(i)))); + float translationY = static_cast(static_cast(font->getMetrics().getHeight() * lineSpacing * static_cast(i))); - dimensions.x = std::max(dimensions.x, item->label->getDimensions().x); + item->rowContainer->setDimensions(Vector2(menuWidth, font->getMetrics().getHeight())); + item->rowContainer->setTranslation(Vector2(0.0f, translationY)); if (!i) { diff --git a/src/ui/menu.hpp b/src/ui/menu.hpp index fb40790..379eb3f 100644 --- a/src/ui/menu.hpp +++ b/src/ui/menu.hpp @@ -38,9 +38,18 @@ public: void setDeselectedCallback(std::function callback); void setActivatedCallback(std::function callback); void setValueChangedCallback(std::function callback); - void setLabel(const std::string& text); - std::size_t getIndex() const; + void setName(const std::string& text); + void addValue(const std::string& text); + void removeValues(); + void setValueIndex(std::size_t index); + + std::size_t getValueCount() const; + const std::string& getValue(std::size_t index) const; + std::size_t getValueIndex() const; + + + std::size_t getItemIndex() const; bool isSelected() const; private: @@ -57,14 +66,34 @@ private: std::function selectedCallback; std::function deselectedCallback; std::function activatedCallback; - UILabel* label; + std::function valueChangedCallback; + std::vector values; + std::size_t valueIndex; + UILabel* nameLabel; + UILabel* valueLabel; + UIContainer* rowContainer; }; -inline std::size_t MenuItem::getIndex() const +inline std::size_t MenuItem::getItemIndex() const { return index; } +inline std::size_t MenuItem::getValueCount() const +{ + return values.size(); +} + +inline const std::string& MenuItem::getValue(std::size_t index) const +{ + return values[index]; +} + +inline std::size_t MenuItem::getValueIndex() const +{ + return valueIndex; +} + class Menu { public: @@ -81,6 +110,7 @@ public: void setExitedCallback(std::function callback); void setFont(Font* font); void setLineSpacing(float spacing); + void setColumnMargin(float margin); std::size_t getItemCount(); const MenuItem* getItem(std::size_t index) const; @@ -120,6 +150,7 @@ private: std::function exitedCallback; Font* font; float lineSpacing; + float columnMargin; UIContainer* container; };