From cdcba7e9beaf155407d4252082993eb2ae37de91 Mon Sep 17 00:00:00 2001 From: "C. J. Howard" Date: Mon, 13 Jun 2022 06:35:00 +0800 Subject: [PATCH] Add graphics menu --- CMakeLists.txt | 1 + src/application.hpp | 8 + src/game/context.hpp | 20 ++ src/game/fonts.cpp | 33 +- src/game/states/boot.cpp | 26 ++ src/game/states/credits.cpp | 2 +- src/game/states/graphics-menu.cpp | 512 ++++++++++++++++++++++++++++++ src/game/states/graphics-menu.hpp | 39 +++ src/game/states/language-menu.cpp | 2 - src/game/states/loading.cpp | 5 +- src/game/states/main-menu.cpp | 4 +- src/game/states/main-menu.hpp | 2 +- src/game/states/options-menu.cpp | 16 +- src/game/states/sound-menu.cpp | 27 +- src/game/states/title.cpp | 9 +- 15 files changed, 658 insertions(+), 48 deletions(-) create mode 100644 src/game/states/graphics-menu.cpp create mode 100644 src/game/states/graphics-menu.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 7f0f857..d3614e6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,6 @@ cmake_minimum_required(VERSION 3.7) + option(VERSION_STRING "Project version string" "0.0.0") project(antkeeper VERSION ${VERSION_STRING} LANGUAGES CXX) diff --git a/src/application.hpp b/src/application.hpp index 5f1e614..ded3810 100644 --- a/src/application.hpp +++ b/src/application.hpp @@ -206,6 +206,9 @@ public: /// Returns `true` if the window is in fullscreen mode, `false` otherwise. bool is_fullscreen() const; + /// Returns `true` if the v-sync is enabled, `false` otherwise. + bool get_vsync() const; + /// Returns the rasterizer for the window. gl::rasterizer* get_rasterizer(); @@ -297,6 +300,11 @@ inline bool application::is_fullscreen() const return fullscreen; } +inline bool application::get_vsync() const +{ + return vsync; +} + inline gl::rasterizer* application::get_rasterizer() { return rasterizer; diff --git a/src/game/context.hpp b/src/game/context.hpp index a9bd9cc..6f037f8 100644 --- a/src/game/context.hpp +++ b/src/game/context.hpp @@ -167,6 +167,7 @@ struct context // Rendering gl::rasterizer* rasterizer; render::renderer* renderer; + float render_resolution_scale; gl::vertex_buffer* billboard_vbo; gl::vertex_array* billboard_vao; render::material* fallback_material; @@ -232,6 +233,25 @@ struct context std::vector sound_menu_label_texts; std::vector sound_menu_value_texts; int sound_menu_index; + scene::text* graphics_menu_display_mode_label_text; + scene::text* graphics_menu_display_mode_value_text; + scene::text* graphics_menu_render_resolution_label_text; + scene::text* graphics_menu_render_resolution_value_text; + scene::text* graphics_menu_v_sync_label_text; + scene::text* graphics_menu_v_sync_value_text; + scene::text* graphics_menu_font_size_label_text; + scene::text* graphics_menu_font_size_value_text; + scene::text* graphics_menu_dyslexia_font_label_text; + scene::text* graphics_menu_dyslexia_font_value_text; + scene::text* graphics_menu_back_label_text; + std::vector graphics_menu_label_texts; + std::vector graphics_menu_value_texts; + int graphics_menu_index; + std::vector> graphics_menu_select_callbacks; + std::vector> graphics_menu_left_callbacks; + std::vector> graphics_menu_right_callbacks; + float font_size; + bool dyslexia_font; // Surface scene scene::collection* surface_scene; diff --git a/src/game/fonts.cpp b/src/game/fonts.cpp index 8f2ed98..45677cb 100644 --- a/src/game/fonts.cpp +++ b/src/game/fonts.cpp @@ -69,27 +69,19 @@ static void build_bitmap_font(const type::typeface& typeface, float size, const void load_fonts(game::context* ctx) { // Load dyslexia-friendly typeface (if enabled) - bool dyslexia_font = false; - if (ctx->config->contains("dyslexia_font")) + bool dyslexia_font_loaded = false; + if (ctx->dyslexia_font) { - dyslexia_font = (*ctx->config)["dyslexia_font"].get(); - - if (dyslexia_font) + if (auto it = ctx->strings->find("font_dyslexia"); it != ctx->strings->end() && !it->second.empty() && it->second[0] != '#') { - if (auto it = ctx->strings->find("font_dyslexia"); it != ctx->strings->end() && !it->second.empty() && it->second[0] != '#') - { - ctx->logger->log(it->second); - ctx->typefaces["dyslexia"] = ctx->resource_manager->load(it->second); - } - else - { - dyslexia_font = false; - } + ctx->logger->log(it->second); + ctx->typefaces["dyslexia"] = ctx->resource_manager->load(it->second); + dyslexia_font_loaded = true; } } // Load typefaces - if (dyslexia_font) + if (dyslexia_font_loaded) { // Override standard typefaces with dyslexia-friendly typeface ctx->typefaces["serif"] = ctx->typefaces["dyslexia"]; @@ -142,13 +134,10 @@ void load_fonts(game::context* ctx) title_font_size_pt = (*ctx->config)["title_font_size"].get(); // Scale font point sizes - if (ctx->config->contains("font_scale")) - { - const float font_scale = (*ctx->config)["font_scale"].get(); - debug_font_size_pt *= font_scale; - menu_font_size_pt *= font_scale; - title_font_size_pt *= font_scale; - } + const float font_size = (*ctx->config)["font_size"].get(); + debug_font_size_pt *= font_size; + menu_font_size_pt *= font_size; + title_font_size_pt *= font_size; // Convert font point sizes to pixel sizes const float dpi = ctx->app->get_display_dpi(); diff --git a/src/game/states/boot.cpp b/src/game/states/boot.cpp index cbc0e9d..0d98852 100644 --- a/src/game/states/boot.cpp +++ b/src/game/states/boot.cpp @@ -112,6 +112,7 @@ static void setup_animation(game::context* ctx); static void setup_entities(game::context* ctx); static void setup_systems(game::context* ctx); static void setup_controls(game::context* ctx); +static void setup_ui(game::context* ctx); static void setup_cli(game::context* ctx); static void setup_callbacks(game::context* ctx); @@ -140,6 +141,7 @@ void enter(application* app, int argc, char** argv) setup_entities(ctx); setup_systems(ctx); setup_controls(ctx); + setup_ui(ctx); setup_cli(ctx); setup_callbacks(ctx); } @@ -463,6 +465,11 @@ void setup_rendering(game::context* ctx) // Get rasterizer from application ctx->rasterizer = ctx->app->get_rasterizer(); + // Load render resolution + ctx->render_resolution_scale = 1.0f; + if (ctx->config->contains("render_resolution")) + ctx->render_resolution_scale = (*ctx->config)["render_resolution"].get(); + // Get default framebuffer const gl::framebuffer& default_framebuffer = ctx->rasterizer->get_default_framebuffer(); const auto& viewport_dimensions = default_framebuffer.get_dimensions(); @@ -970,6 +977,25 @@ void setup_controls(game::context* ctx) } } +void setup_ui(game::context* ctx) +{ + // Load font size config + ctx->font_size = 1.0f; + if (ctx->config->contains("font_size")) + ctx->font_size = (*ctx->config)["font_size"].get(); + + // Load dyslexia font config + ctx->dyslexia_font = false; + if (ctx->config->contains("dyslexia_font")) + ctx->dyslexia_font = (*ctx->config)["dyslexia_font"].get(); + + ctx->main_menu_index = 0; + ctx->options_menu_index = 0; + ctx->graphics_menu_index = 0; + ctx->sound_menu_index = 0; + ctx->language_menu_index = 0; +} + void setup_cli(game::context* ctx) { ctx->cli = new debug::cli(); diff --git a/src/game/states/credits.cpp b/src/game/states/credits.cpp index 32fa6ea..5cf6795 100644 --- a/src/game/states/credits.cpp +++ b/src/game/states/credits.cpp @@ -102,7 +102,7 @@ void enter(game::context* ctx) // Change state application::state next_state; next_state.name = "main_menu"; - next_state.enter = std::bind(game::state::main_menu::enter, ctx, 2); + next_state.enter = std::bind(game::state::main_menu::enter, ctx); next_state.exit = std::bind(game::state::main_menu::exit, ctx); ctx->app->change_state(next_state); } diff --git a/src/game/states/graphics-menu.cpp b/src/game/states/graphics-menu.cpp new file mode 100644 index 0000000..00d6237 --- /dev/null +++ b/src/game/states/graphics-menu.cpp @@ -0,0 +1,512 @@ +/* + * Copyright (C) 2021 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#include "game/states/graphics-menu.hpp" +#include "game/states/options-menu.hpp" +#include "application.hpp" +#include "scene/text.hpp" +#include "render/passes/clear-pass.hpp" +#include "debug/logger.hpp" +#include "game/fonts.hpp" + +namespace game { +namespace state { +namespace graphics_menu { + +static void update_text_color(game::context* ctx) +{ + float4 inactive_color = {1.0f, 1.0f, 1.0f, 0.5f}; + float4 active_color = {1.0f, 1.0f, 1.0f, 1.0f}; + + for (std::size_t i = 0; i < ctx->graphics_menu_label_texts.size(); ++i) + { + scene::text* label_text = ctx->graphics_menu_label_texts[i]; + scene::text* value_text = ctx->graphics_menu_value_texts[i]; + + if (i == ctx->graphics_menu_index) + { + label_text->set_color(active_color); + if (value_text) + value_text->set_color(active_color); + } + else + { + label_text->set_color(inactive_color); + if (value_text) + value_text->set_color(inactive_color); + } + } +} + +static void update_text_tweens(game::context* ctx) +{ + for (std::size_t i = 0; i < ctx->graphics_menu_label_texts.size(); ++i) + { + scene::text* label_text = ctx->graphics_menu_label_texts[i]; + scene::text* value_text = ctx->graphics_menu_value_texts[i]; + + label_text->update_tweens(); + if (value_text) + value_text->update_tweens(); + } +} + +static void refresh_texts(game::context* ctx) +{ + for (std::size_t i = 0; i < ctx->graphics_menu_label_texts.size(); ++i) + { + scene::text* label_text = ctx->graphics_menu_label_texts[i]; + label_text->refresh(); + + scene::text* value_text = ctx->graphics_menu_value_texts[i]; + if (value_text) + value_text->refresh(); + } +} + +void enter(game::context* ctx) +{ + ctx->ui_clear_pass->set_cleared_buffers(true, true, false); + + // Construct graphics menu texts + ctx->graphics_menu_display_mode_label_text = new scene::text(); + ctx->graphics_menu_display_mode_value_text = new scene::text(); + ctx->graphics_menu_render_resolution_label_text = new scene::text(); + ctx->graphics_menu_render_resolution_value_text = new scene::text(); + ctx->graphics_menu_v_sync_label_text = new scene::text(); + ctx->graphics_menu_v_sync_value_text = new scene::text(); + ctx->graphics_menu_font_size_label_text = new scene::text(); + ctx->graphics_menu_font_size_value_text = new scene::text(); + ctx->graphics_menu_dyslexia_font_label_text = new scene::text(); + ctx->graphics_menu_dyslexia_font_value_text = new scene::text(); + ctx->graphics_menu_back_label_text = new scene::text(); + + bool fullscreen = ctx->app->is_fullscreen(); + float render_resolution = ctx->render_resolution_scale; + bool vsync = ctx->app->get_vsync(); + float font_size = ctx->font_size; + bool dyslexia_font = ctx->dyslexia_font; + + std::string string_fullscreen = (*ctx->strings)["graphics_menu_display_mode_fullscreen"]; + std::string string_window = (*ctx->strings)["graphics_menu_display_mode_window"]; + std::string string_on = (*ctx->strings)["on"]; + std::string string_off = (*ctx->strings)["off"]; + + // Set text content + ctx->graphics_menu_display_mode_label_text->set_content((*ctx->strings)["graphics_menu_display_mode"]); + ctx->graphics_menu_display_mode_value_text->set_content((fullscreen) ? string_fullscreen : string_window); + ctx->graphics_menu_render_resolution_label_text->set_content((*ctx->strings)["graphics_menu_render_resolution"]); + ctx->graphics_menu_render_resolution_value_text->set_content(std::to_string(static_cast(std::round(render_resolution * 100.0f))) + "%"); + ctx->graphics_menu_v_sync_label_text->set_content((*ctx->strings)["graphics_menu_v_sync"]); + ctx->graphics_menu_v_sync_value_text->set_content((vsync) ? string_on : string_off); + + ctx->graphics_menu_font_size_label_text->set_content((*ctx->strings)["graphics_menu_font_size"]); + ctx->graphics_menu_font_size_value_text->set_content(std::to_string(static_cast(std::round(font_size * 100.0f))) + "%"); + + ctx->graphics_menu_dyslexia_font_label_text->set_content((*ctx->strings)["graphics_menu_dyslexia_font"]); + ctx->graphics_menu_dyslexia_font_value_text->set_content((dyslexia_font) ? string_on : string_off); + + ctx->graphics_menu_back_label_text->set_content((*ctx->strings)["back"]); + + // Build lists of graphics menu texts + ctx->graphics_menu_label_texts.push_back(ctx->graphics_menu_display_mode_label_text); + ctx->graphics_menu_label_texts.push_back(ctx->graphics_menu_render_resolution_label_text); + ctx->graphics_menu_label_texts.push_back(ctx->graphics_menu_v_sync_label_text); + ctx->graphics_menu_label_texts.push_back(ctx->graphics_menu_font_size_label_text); + ctx->graphics_menu_label_texts.push_back(ctx->graphics_menu_dyslexia_font_label_text); + ctx->graphics_menu_label_texts.push_back(ctx->graphics_menu_back_label_text); + ctx->graphics_menu_value_texts.push_back(ctx->graphics_menu_display_mode_value_text); + ctx->graphics_menu_value_texts.push_back(ctx->graphics_menu_render_resolution_value_text); + ctx->graphics_menu_value_texts.push_back(ctx->graphics_menu_v_sync_value_text); + ctx->graphics_menu_value_texts.push_back(ctx->graphics_menu_font_size_value_text); + ctx->graphics_menu_value_texts.push_back(ctx->graphics_menu_dyslexia_font_value_text); + ctx->graphics_menu_value_texts.push_back(nullptr); + + // Set text fonts + for (std::size_t i = 0; i < ctx->graphics_menu_label_texts.size(); ++i) + { + scene::text* label_text = ctx->graphics_menu_label_texts[i]; + label_text->set_material(&ctx->menu_font_material); + label_text->set_font(&ctx->menu_font); + + scene::text* value_text = ctx->graphics_menu_value_texts[i]; + if (value_text) + { + value_text->set_material(&ctx->menu_font_material); + value_text->set_font(&ctx->menu_font); + } + } + + // Calculate menu width + float menu_width = 0.0f; + float menu_spacing = ctx->menu_font.get_glyph_metrics(U'M').width; + for (std::size_t i = 0; i < ctx->graphics_menu_label_texts.size(); ++i) + { + scene::text* label_text = ctx->graphics_menu_label_texts[i]; + scene::text* value_text = ctx->graphics_menu_value_texts[i]; + + float row_width = 0.0f; + + // Add label width to width + const auto& label_bounds = static_cast&>(label_text->get_local_bounds()); + row_width += label_bounds.max_point.x - label_bounds.min_point.x; + + if (value_text != nullptr) + { + // Add value width to width + //const auto& value_bounds = static_cast&>(value_text->get_local_bounds()); + //row_width += value_bounds.max_point.x - value_bounds.min_point.x; + + // Add spacing to row width + row_width += menu_spacing * 8.0f; + } + + menu_width = std::max(menu_width, row_width); + } + + // Align texts + float menu_height = ctx->graphics_menu_label_texts.size() * ctx->menu_font.get_font_metrics().linespace; + float menu_x = -menu_width * 0.5f; + float menu_y = menu_height * 0.5f - ctx->menu_font.get_font_metrics().linespace; + for (std::size_t i = 0; i < ctx->graphics_menu_label_texts.size(); ++i) + { + scene::text* label_text = ctx->graphics_menu_label_texts[i]; + + float x = menu_x; + float y = menu_y - ctx->menu_font.get_font_metrics().linespace * i; + label_text->set_translation({std::round(x), std::round(y), 0.0f}); + + scene::text* value_text = ctx->graphics_menu_value_texts[i]; + if (value_text) + { + const auto& value_bounds = static_cast&>(value_text->get_local_bounds()); + const float value_width = value_bounds.max_point.x - value_bounds.min_point.x; + x = menu_x + menu_width - value_width; + + value_text->set_translation({std::round(x), std::round(y), 0.0f}); + } + } + + // Construct graphics menu callbacks + auto menu_back_callback = [ctx]() + { + application::state next_state; + next_state.name = "options_menu"; + next_state.enter = std::bind(game::state::options_menu::enter, ctx); + next_state.exit = std::bind(game::state::options_menu::exit, ctx); + ctx->app->change_state(next_state); + }; + + ctx->controls["menu_down"]->set_activated_callback + ( + [ctx]() + { + ++ctx->graphics_menu_index; + if (ctx->graphics_menu_index >= ctx->graphics_menu_label_texts.size()) + ctx->graphics_menu_index = 0; + + update_text_color(ctx); + } + ); + ctx->controls["menu_up"]->set_activated_callback + ( + [ctx]() + { + --ctx->graphics_menu_index; + if (ctx->graphics_menu_index < 0) + ctx->graphics_menu_index = ctx->graphics_menu_label_texts.size() - 1; + + update_text_color(ctx); + } + ); + ctx->controls["menu_left"]->set_activated_callback + ( + [ctx]() + { + if (ctx->graphics_menu_left_callbacks[ctx->graphics_menu_index]) + ctx->graphics_menu_left_callbacks[ctx->graphics_menu_index](); + } + ); + ctx->controls["menu_right"]->set_activated_callback + ( + [ctx]() + { + if (ctx->graphics_menu_right_callbacks[ctx->graphics_menu_index]) + ctx->graphics_menu_right_callbacks[ctx->graphics_menu_index](); + } + ); + ctx->controls["menu_select"]->set_activated_callback + ( + [ctx]() + { + if (ctx->graphics_menu_select_callbacks[ctx->graphics_menu_index]) + ctx->graphics_menu_select_callbacks[ctx->graphics_menu_index](); + } + ); + ctx->controls["menu_back"]->set_activated_callback(menu_back_callback); + + auto toggle_fullscreen_callback = [ctx]() + { + bool fullscreen = !ctx->app->is_fullscreen(); + + ctx->app->set_fullscreen(fullscreen); + + if (!fullscreen) + { + int2 resolution; + resolution.x = (*ctx->config)["windowed_resolution"][0].get(); + resolution.y = (*ctx->config)["windowed_resolution"][1].get(); + + ctx->app->resize_window(resolution.x, resolution.y); + } + + const std::string string_fullscreen = (*ctx->strings)["graphics_menu_display_mode_fullscreen"]; + const std::string string_window = (*ctx->strings)["graphics_menu_display_mode_window"];; + ctx->graphics_menu_display_mode_value_text->set_content((fullscreen) ? string_fullscreen : string_window); + + // Save display mode config + (*ctx->config)["fullscreen"] = fullscreen; + }; + + auto increase_render_resolution_callback = [ctx]() + { + // Increase resolution + if (ctx->controls["menu_modifier"]->is_active()) + ctx->render_resolution_scale += 0.01f; + else + ctx->render_resolution_scale += 0.1f; + + // Limit resolution + if (ctx->render_resolution_scale > 2.0f) + ctx->render_resolution_scale = 2.0f; + + // Update value text + ctx->graphics_menu_render_resolution_value_text->set_content(std::to_string(static_cast(std::round(ctx->render_resolution_scale * 100.0f))) + "%"); + + // Update config + (*ctx->config)["render_resolution"] = ctx->render_resolution_scale; + }; + + auto decrease_render_resolution_callback = [ctx]() + { + // Increase resolution + if (ctx->controls["menu_modifier"]->is_active()) + ctx->render_resolution_scale -= 0.01f; + else + ctx->render_resolution_scale -= 0.1f; + + // Limit resolution + if (ctx->render_resolution_scale < 0.1f) + ctx->render_resolution_scale = 0.1f; + + // Update value text + ctx->graphics_menu_render_resolution_value_text->set_content(std::to_string(static_cast(std::round(ctx->render_resolution_scale * 100.0f))) + "%"); + + // Update config + (*ctx->config)["render_resolution"] = ctx->render_resolution_scale; + }; + + auto increase_font_size_callback = [ctx]() + { + // Increase font size + if (ctx->controls["menu_modifier"]->is_active()) + ctx->font_size += 0.01f; + else + ctx->font_size += 0.1f; + + // Limit font size + if (ctx->font_size > 2.0f) + ctx->font_size = 2.0f; + + // Update value text + ctx->graphics_menu_font_size_value_text->set_content(std::to_string(static_cast(std::round(ctx->font_size * 100.0f))) + "%"); + + // Update config + (*ctx->config)["font_size"] = ctx->font_size; + + // Reload fonts + ctx->logger->push_task("Reloading fonts"); + try + { + game::load_fonts(ctx); + } + catch (...) + { + ctx->logger->pop_task(EXIT_FAILURE); + } + ctx->logger->pop_task(EXIT_SUCCESS); + + // Refresh text + refresh_texts(ctx); + }; + + auto decrease_font_size_callback = [ctx]() + { + // Decrease font size + if (ctx->controls["menu_modifier"]->is_active()) + ctx->font_size -= 0.01f; + else + ctx->font_size -= 0.1f; + + // Limit font size + if (ctx->font_size < 0.1f) + ctx->font_size = 0.1f; + + // Update value text + ctx->graphics_menu_font_size_value_text->set_content(std::to_string(static_cast(std::round(ctx->font_size * 100.0f))) + "%"); + + // Update config + (*ctx->config)["font_size"] = ctx->font_size; + + // Reload fonts + ctx->logger->push_task("Reloading fonts"); + try + { + game::load_fonts(ctx); + } + catch (...) + { + ctx->logger->pop_task(EXIT_FAILURE); + } + ctx->logger->pop_task(EXIT_SUCCESS); + + // Refresh text + refresh_texts(ctx); + }; + + auto toggle_vsync_callback = [ctx]() + { + bool vsync = !ctx->app->get_vsync(); + + ctx->app->set_vsync(vsync); + + std::string string_on = (*ctx->strings)["on"]; + std::string string_off = (*ctx->strings)["off"]; + ctx->graphics_menu_v_sync_value_text->set_content((vsync) ? string_on : string_off); + + // Save v-sync config + (*ctx->config)["vsync"] = vsync; + }; + + auto toggle_dyslexia_font_callback = [ctx]() + { + ctx->dyslexia_font = !ctx->dyslexia_font; + + std::string string_on = (*ctx->strings)["on"]; + std::string string_off = (*ctx->strings)["off"]; + ctx->graphics_menu_dyslexia_font_value_text->set_content((ctx->dyslexia_font) ? string_on : string_off); + + // Save dyslexia font config + (*ctx->config)["dyslexia_font"] = ctx->dyslexia_font; + + // Reload fonts + ctx->logger->push_task("Reloading fonts"); + try + { + game::load_fonts(ctx); + } + catch (...) + { + ctx->logger->pop_task(EXIT_FAILURE); + } + ctx->logger->pop_task(EXIT_SUCCESS); + + // Refresh text + refresh_texts(ctx); + }; + + // Build list of graphics menu callbacks + ctx->graphics_menu_select_callbacks.push_back(toggle_fullscreen_callback); + ctx->graphics_menu_select_callbacks.push_back(increase_render_resolution_callback); + ctx->graphics_menu_select_callbacks.push_back(toggle_vsync_callback); + ctx->graphics_menu_select_callbacks.push_back(increase_font_size_callback); + ctx->graphics_menu_select_callbacks.push_back(toggle_dyslexia_font_callback); + ctx->graphics_menu_select_callbacks.push_back(menu_back_callback); + + ctx->graphics_menu_left_callbacks.push_back(toggle_fullscreen_callback); + ctx->graphics_menu_left_callbacks.push_back(decrease_render_resolution_callback); + ctx->graphics_menu_left_callbacks.push_back(toggle_vsync_callback); + ctx->graphics_menu_left_callbacks.push_back(decrease_font_size_callback); + ctx->graphics_menu_left_callbacks.push_back(toggle_dyslexia_font_callback); + ctx->graphics_menu_left_callbacks.push_back(nullptr); + + ctx->graphics_menu_right_callbacks.push_back(toggle_fullscreen_callback); + ctx->graphics_menu_right_callbacks.push_back(increase_render_resolution_callback); + ctx->graphics_menu_right_callbacks.push_back(toggle_vsync_callback); + ctx->graphics_menu_right_callbacks.push_back(increase_font_size_callback); + ctx->graphics_menu_right_callbacks.push_back(toggle_dyslexia_font_callback); + ctx->graphics_menu_right_callbacks.push_back(nullptr); + + // Add text objects to UI + for (std::size_t i = 0; i < ctx->graphics_menu_label_texts.size(); ++i) + { + scene::text* label_text = ctx->graphics_menu_label_texts[i]; + scene::text* value_text = ctx->graphics_menu_value_texts[i]; + + ctx->ui_scene->add_object(label_text); + if (value_text) + ctx->ui_scene->add_object(value_text); + } + update_text_color(ctx); + update_text_tweens(ctx); +} + +void exit(game::context* ctx) +{ + // Clear control callbacks + ctx->controls["menu_down"]->set_activated_callback(nullptr); + ctx->controls["menu_up"]->set_activated_callback(nullptr); + ctx->controls["menu_left"]->set_activated_callback(nullptr); + ctx->controls["menu_right"]->set_activated_callback(nullptr); + ctx->controls["menu_select"]->set_activated_callback(nullptr); + ctx->controls["menu_back"]->set_activated_callback(nullptr); + + // Clear menu callbacks + ctx->graphics_menu_select_callbacks.clear(); + ctx->graphics_menu_left_callbacks.clear(); + ctx->graphics_menu_right_callbacks.clear(); + + // Destruct graphics menu texts + for (std::size_t i = 0; i < ctx->graphics_menu_label_texts.size(); ++i) + { + scene::text* label_text = ctx->graphics_menu_label_texts[i]; + ctx->ui_scene->remove_object(label_text); + delete label_text; + + scene::text* value_text = ctx->graphics_menu_value_texts[i]; + if (value_text) + { + ctx->ui_scene->remove_object(value_text); + delete value_text; + } + } + ctx->graphics_menu_label_texts.clear(); + ctx->graphics_menu_value_texts.clear(); + + // Update volumes in config + //(*ctx->config)["master_volume"] = ctx->master_volume; + //(*ctx->config)["ambience_volume"] = ctx->ambience_volume; + //(*ctx->config)["effects_volume"] = ctx->effects_volume; + + ctx->ui_clear_pass->set_cleared_buffers(false, true, false); +} + +} // namespace graphics_menu +} // namespace state +} // namespace game diff --git a/src/game/states/graphics-menu.hpp b/src/game/states/graphics-menu.hpp new file mode 100644 index 0000000..8a6c246 --- /dev/null +++ b/src/game/states/graphics-menu.hpp @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2021 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_GAME_STATE_GRAPHICS_MENU_HPP +#define ANTKEEPER_GAME_STATE_GRAPHICS_MENU_HPP + +#include "game/context.hpp" + +namespace game { +namespace state { + +/// Sound menu screen game state functions. +namespace graphics_menu { + +void enter(game::context* ctx); +void exit(game::context* ctx); + +} // namespace graphics_menu + +} // namespace state +} // namespace game + +#endif // ANTKEEPER_GAME_STATE_GRAPHICS_MENU_HPP diff --git a/src/game/states/language-menu.cpp b/src/game/states/language-menu.cpp index a26d990..ca49375 100644 --- a/src/game/states/language-menu.cpp +++ b/src/game/states/language-menu.cpp @@ -111,8 +111,6 @@ void enter(game::context* ctx) { ctx->ui_clear_pass->set_cleared_buffers(true, true, false); - ctx->language_menu_index = 0; - // Construct language menu texts ctx->language_menu_language_text = new scene::text(); ctx->language_menu_back_text = new scene::text(); diff --git a/src/game/states/loading.cpp b/src/game/states/loading.cpp index d7e2d87..4b9ca3d 100644 --- a/src/game/states/loading.cpp +++ b/src/game/states/loading.cpp @@ -35,6 +35,7 @@ #include "game/states/splash.hpp" #include "game/states/main-menu.hpp" #include "game/controls.hpp" +#include "game/save.hpp" #include "geom/spherical.hpp" #include "gl/drawing-mode.hpp" #include "gl/vertex-array.hpp" @@ -129,7 +130,7 @@ void enter(game::context* ctx) if (ctx->option_quick_start.has_value()) { next_state.name = "main_menu"; - next_state.enter = std::bind(game::state::main_menu::enter, ctx, 0); + next_state.enter = std::bind(game::state::main_menu::enter, ctx); next_state.exit = std::bind(game::state::main_menu::exit, ctx); } else @@ -204,7 +205,9 @@ void load_controls(game::context* ctx) ctx->app->resize_window(resolution.x, resolution.y); } + // Save display mode config (*ctx->config)["fullscreen"] = fullscreen; + game::save_config(ctx); } ); diff --git a/src/game/states/main-menu.cpp b/src/game/states/main-menu.cpp index 54be249..ffd31fa 100644 --- a/src/game/states/main-menu.cpp +++ b/src/game/states/main-menu.cpp @@ -38,10 +38,8 @@ namespace game { namespace state { namespace main_menu { -void enter(game::context* ctx, int main_menu_index) +void enter(game::context* ctx) { - ctx->main_menu_index = main_menu_index; - ctx->ui_clear_pass->set_cleared_buffers(true, true, false); // Construct main menu texts diff --git a/src/game/states/main-menu.hpp b/src/game/states/main-menu.hpp index 73bce10..4164422 100644 --- a/src/game/states/main-menu.hpp +++ b/src/game/states/main-menu.hpp @@ -28,7 +28,7 @@ namespace state { /// Main menu screen game state functions. namespace main_menu { -void enter(game::context* ctx, int main_menu_index); +void enter(game::context* ctx); void exit(game::context* ctx); } // namespace main_menu diff --git a/src/game/states/options-menu.cpp b/src/game/states/options-menu.cpp index bd9e909..0680d8a 100644 --- a/src/game/states/options-menu.cpp +++ b/src/game/states/options-menu.cpp @@ -19,6 +19,8 @@ #include "game/states/options-menu.hpp" #include "game/states/main-menu.hpp" +#include "game/states/graphics-menu.hpp" +#include "game/states/graphics-menu.hpp" #include "game/states/sound-menu.hpp" #include "game/states/language-menu.hpp" #include "game/save.hpp" @@ -37,8 +39,6 @@ void enter(game::context* ctx) { ctx->ui_clear_pass->set_cleared_buffers(true, true, false); - ctx->options_menu_index = 0; - // Construct options menu texts ctx->options_menu_controls_text = new scene::text(); ctx->options_menu_graphics_text = new scene::text(); @@ -62,10 +62,18 @@ void enter(game::context* ctx) // Return to main menu application::state next_state; next_state.name = "main_menu"; - next_state.enter = std::bind(game::state::main_menu::enter, ctx, 1); + next_state.enter = std::bind(game::state::main_menu::enter, ctx); next_state.exit = std::bind(game::state::main_menu::exit, ctx); ctx->app->change_state(next_state); }; + auto change_state_graphics_menu = [ctx]() + { + application::state next_state; + next_state.name = "graphics_menu"; + next_state.enter = std::bind(game::state::graphics_menu::enter, ctx); + next_state.exit = std::bind(game::state::graphics_menu::exit, ctx); + ctx->app->change_state(next_state); + }; auto change_state_sound_menu = [ctx]() { application::state next_state; @@ -85,7 +93,7 @@ void enter(game::context* ctx) // Build list of options menu callbacks ctx->options_menu_callbacks.push_back(nullptr); - ctx->options_menu_callbacks.push_back(nullptr); + ctx->options_menu_callbacks.push_back(change_state_graphics_menu); ctx->options_menu_callbacks.push_back(change_state_sound_menu); ctx->options_menu_callbacks.push_back(change_state_language_menu); ctx->options_menu_callbacks.push_back(menu_back_callback); diff --git a/src/game/states/sound-menu.cpp b/src/game/states/sound-menu.cpp index 4543ec6..a620e7c 100644 --- a/src/game/states/sound-menu.cpp +++ b/src/game/states/sound-menu.cpp @@ -53,7 +53,6 @@ static void update_text_color(game::context* ctx) } } - static void update_text_tweens(game::context* ctx) { for (std::size_t i = 0; i < ctx->sound_menu_label_texts.size(); ++i) @@ -71,8 +70,6 @@ void enter(game::context* ctx) { ctx->ui_clear_pass->set_cleared_buffers(true, true, false); - ctx->sound_menu_index = 0; - // Construct sound menu texts ctx->sound_menu_master_volume_label_text = new scene::text(); ctx->sound_menu_master_volume_value_text = new scene::text(); @@ -84,11 +81,11 @@ void enter(game::context* ctx) // Set text content ctx->sound_menu_master_volume_label_text->set_content((*ctx->strings)["sound_menu_master_volume"]); - ctx->sound_menu_master_volume_value_text->set_content(std::to_string(static_cast(ctx->master_volume * 100.0f + 0.5f))); + ctx->sound_menu_master_volume_value_text->set_content(std::to_string(static_cast(ctx->master_volume * 100.0f + 0.5f)) + "%"); ctx->sound_menu_ambience_volume_label_text->set_content((*ctx->strings)["sound_menu_ambience_volume"]); - ctx->sound_menu_ambience_volume_value_text->set_content(std::to_string(static_cast(ctx->ambience_volume * 100.0f + 0.5f))); + ctx->sound_menu_ambience_volume_value_text->set_content(std::to_string(static_cast(ctx->ambience_volume * 100.0f + 0.5f)) + "%"); ctx->sound_menu_effects_volume_label_text->set_content((*ctx->strings)["sound_menu_effects_volume"]); - ctx->sound_menu_effects_volume_value_text->set_content(std::to_string(static_cast(ctx->effects_volume * 100.0f + 0.5f))); + ctx->sound_menu_effects_volume_value_text->set_content(std::to_string(static_cast(ctx->effects_volume * 100.0f + 0.5f)) + "%"); ctx->sound_menu_back_label_text->set_content((*ctx->strings)["back"]); // Build lists of sound menu texts @@ -190,13 +187,18 @@ void enter(game::context* ctx) float* volume = volumes[ctx->sound_menu_index]; // Increase volume - *volume += 0.1f; + if (ctx->controls["menu_modifier"]->is_active()) + *volume += 0.01f; + else + *volume += 0.1f; + + // Limit volume if (*volume > 1.0f) *volume = 1.0f; // Update volume value text scene::text* value_text = ctx->sound_menu_value_texts[ctx->sound_menu_index]; - value_text->set_content(std::to_string(static_cast((*volume) * 100.0f + 0.5f))); + value_text->set_content(std::to_string(static_cast((*volume) * 100.0f + 0.5f)) + "%"); // Realign value text const auto& value_bounds = static_cast&>(value_text->get_local_bounds()); @@ -220,13 +222,18 @@ void enter(game::context* ctx) float* volume = volumes[ctx->sound_menu_index]; // Decrease volume - *volume -= 0.1f; + if (ctx->controls["menu_modifier"]->is_active()) + *volume -= 0.01f; + else + *volume -= 0.1f; + + // Limit volume if (*volume < 0.0f) *volume = 0.0f; // Update volume value text scene::text* value_text = ctx->sound_menu_value_texts[ctx->sound_menu_index]; - value_text->set_content(std::to_string(static_cast((*volume) * 100.0f + 0.5f))); + value_text->set_content(std::to_string(static_cast((*volume) * 100.0f + 0.5f)) + "%"); // Realign value text const auto& value_bounds = static_cast&>(value_text->get_local_bounds()); diff --git a/src/game/states/title.cpp b/src/game/states/title.cpp index f8ac806..adc35aa 100644 --- a/src/game/states/title.cpp +++ b/src/game/states/title.cpp @@ -116,7 +116,7 @@ void enter(game::context* ctx) { application::state next_state; next_state.name = "main_menu"; - next_state.enter = std::bind(game::state::main_menu::enter, ctx, 0); + next_state.enter = std::bind(game::state::main_menu::enter, ctx); next_state.exit = std::bind(game::state::main_menu::exit, ctx); ctx->app->queue_state(next_state); } @@ -151,6 +151,9 @@ void enter(game::context* ctx) ( [ctx](const event_base& event) { + if (ctx->controls["menu_back"]->is_active()) + return; + auto id = event.get_event_type_id(); if (id != mouse_moved_event::event_type_id && id != mouse_wheel_scrolled_event::event_type_id && id != gamepad_axis_moved_event::event_type_id) { @@ -174,11 +177,9 @@ void enter(game::context* ctx) // Change state application::state next_state; next_state.name = "main_menu"; - next_state.enter = std::bind(game::state::main_menu::enter, ctx, 0); + next_state.enter = std::bind(game::state::main_menu::enter, ctx); next_state.exit = std::bind(game::state::main_menu::exit, ctx); ctx->app->change_state(next_state); - - } } }