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/game/menu.cpp b/src/game/menu.cpp index d2e7b61..7a0648d 100644 --- a/src/game/menu.cpp +++ b/src/game/menu.cpp @@ -77,7 +77,7 @@ void update_text_tweens(game::context* ctx) } } -void align_text(game::context* ctx) +void align_text(game::context* ctx, bool center, bool has_back, float anchor_y) { // Calculate menu width float menu_width = 0.0f; @@ -105,23 +105,44 @@ void align_text(game::context* ctx) } // Align texts - float menu_height = ctx->menu_item_texts.size() * ctx->menu_font.get_font_metrics().linespace; + float menu_height; + if (has_back) + menu_height = (ctx->menu_item_texts.size() - 1) * ctx->menu_font.get_font_metrics().linespace - ctx->menu_font.get_font_metrics().linegap; + else + menu_height = ctx->menu_item_texts.size() * ctx->menu_font.get_font_metrics().linespace - ctx->menu_font.get_font_metrics().linegap; + float menu_x = -menu_width * 0.5f; - float menu_y = menu_height * 0.5f - ctx->menu_font.get_font_metrics().linespace; + float menu_y = anchor_y + menu_height * 0.5f - ctx->menu_font.get_font_metrics().size; for (std::size_t i = 0; i < ctx->menu_item_texts.size(); ++i) { auto [name, value] = ctx->menu_item_texts[i]; + float x = menu_x; float y = menu_y - ctx->menu_font.get_font_metrics().linespace * i; + if (has_back && i == ctx->menu_item_texts.size() - 1) + y -= ctx->menu_font.get_font_metrics().linespace; + + + if (center || i == ctx->menu_item_texts.size() - 1) + { + const auto& name_bounds = static_cast&>(name->get_local_bounds()); + const float name_width = name_bounds.max_point.x - name_bounds.min_point.x; + x = -name_width * 0.5f; + } + name->set_translation({std::round(x), std::round(y), 0.0f}); if (value) { const auto& value_bounds = static_cast&>(value->get_local_bounds()); const float value_width = value_bounds.max_point.x - value_bounds.min_point.x; - x = menu_x + menu_width - value_width; + + if (center || i == ctx->menu_item_texts.size() - 1) + x = -value_width * 0.5f; + else + x = menu_x + menu_width - value_width; value->set_translation({std::round(x), std::round(y), 0.0f}); } @@ -242,6 +263,8 @@ void setup_controls(game::context* ctx) ( [ctx](const mouse_moved_event& event) { + const float padding = game::menu::mouseover_padding * ctx->menu_font.get_font_metrics().size; + for (std::size_t i = 0; i < ctx->menu_item_texts.size(); ++i) { auto [name, value] = ctx->menu_item_texts[i]; @@ -264,6 +287,11 @@ void setup_controls(game::context* ctx) const float x = static_cast(event.x - viewport[0] / 2); const float y = static_cast((viewport[1] - event.y + 1) - viewport[1] / 2); + min_x -= padding; + min_y -= padding; + max_x += padding; + max_y += padding; + if (x >= min_x && x <= max_x) { if (y >= min_y && y <= max_y) diff --git a/src/game/menu.hpp b/src/game/menu.hpp index c5e4223..91a3544 100644 --- a/src/game/menu.hpp +++ b/src/game/menu.hpp @@ -34,6 +34,9 @@ static constexpr float4 active_color{1.0f, 1.0f, 1.0f, 1.0f}; /// RGBA color of inactive menu items. static constexpr float4 inactive_color{1.0f, 1.0f, 1.0f, 0.5f}; +/// Padding of the mouseover bounds, as a percentage of the font size. +static constexpr float mouseover_padding = 0.1f; + void init_menu_item_index(game::context* ctx, const std::string& menu_name); void setup_controls(game::context* ctx); @@ -45,7 +48,7 @@ void delete_text(game::context* ctx); void update_text_color(game::context* ctx); void update_text_font(game::context* ctx); void update_text_tweens(game::context* ctx); -void align_text(game::context* ctx); +void align_text(game::context* ctx, bool center = false, bool has_back = true, float anchor_y = 0.0f); void refresh_text(game::context* ctx); void add_text_to_ui(game::context* ctx); diff --git a/src/game/states/gamepad-config-menu.cpp b/src/game/states/gamepad-config-menu.cpp index 10186f6..1ff433d 100644 --- a/src/game/states/gamepad-config-menu.cpp +++ b/src/game/states/gamepad-config-menu.cpp @@ -240,6 +240,14 @@ static void add_control_item(game::context* ctx, const std::string& control_name const gamepad_button_pressed_event& button_event = static_cast(event); ctx->input_event_router->add_mapping(input::gamepad_button_mapping(control, nullptr, button_event.button)); } + else if (id == key_pressed_event::event_type_id) + { + // Map key pressed event to control + const key_pressed_event& key_event = static_cast(event); + + if (key_event.scancode != input::scancode::escape && key_event.scancode != input::scancode::backspace) + return; + } else { return; diff --git a/src/game/states/keyboard-config-menu.cpp b/src/game/states/keyboard-config-menu.cpp index 69cba9c..e895f47 100644 --- a/src/game/states/keyboard-config-menu.cpp +++ b/src/game/states/keyboard-config-menu.cpp @@ -174,7 +174,9 @@ static void add_control_item(game::context* ctx, const std::string& control_name { // Map key pressed event to control const key_pressed_event& key_event = static_cast(event); - ctx->input_event_router->add_mapping(input::key_mapping(control, key_event.keyboard, key_event.scancode)); + + if (key_event.scancode != input::scancode::escape && key_event.scancode != input::scancode::backspace) + ctx->input_event_router->add_mapping(input::key_mapping(control, key_event.keyboard, key_event.scancode)); } else if (id == mouse_wheel_scrolled_event::event_type_id) { diff --git a/src/game/states/main-menu.cpp b/src/game/states/main-menu.cpp index 7506cd5..075203a 100644 --- a/src/game/states/main-menu.cpp +++ b/src/game/states/main-menu.cpp @@ -18,7 +18,6 @@ */ #include "game/states/main-menu.hpp" -#include "game/states/title.hpp" #include "game/states/options-menu.hpp" #include "game/states/forage.hpp" #include "game/states/nuptial-flight.hpp" @@ -43,6 +42,23 @@ void enter(game::context* ctx) { ctx->ui_clear_pass->set_cleared_buffers(true, true, false); + // Construct title text + ctx->title_text = new scene::text(); + ctx->title_text->set_material(&ctx->title_font_material); + ctx->title_text->set_font(&ctx->title_font); + ctx->title_text->set_color({1.0f, 1.0f, 1.0f, 1.0f}); + ctx->title_text->set_content((*ctx->strings)["title_antkeeper"]); + + // Align title text + const auto& title_aabb = static_cast&>(ctx->title_text->get_local_bounds()); + float title_w = title_aabb.max_point.x - title_aabb.min_point.x; + float title_h = title_aabb.max_point.y - title_aabb.min_point.y; + ctx->title_text->set_translation({std::round(-title_w * 0.5f), std::round(-title_h * 0.5f + (std::get<1>(ctx->app->get_viewport_dimensions()) / 3.0f) / 2.0f), 0.0f}); + + // Add title text to UI + ctx->ui_scene->add_object(ctx->title_text); + ctx->title_text->update_tweens(); + // Construct menu item texts scene::text* start_text = new scene::text(); scene::text* options_text = new scene::text(); @@ -66,7 +82,7 @@ void enter(game::context* ctx) game::menu::update_text_color(ctx); game::menu::update_text_font(ctx); - game::menu::align_text(ctx); + game::menu::align_text(ctx, true, false, (-std::get<1>(ctx->app->get_viewport_dimensions()) / 3.0f) / 2.0f); game::menu::update_text_tweens(ctx); game::menu::add_text_to_ui(ctx); @@ -118,14 +134,6 @@ void enter(game::context* ctx) { ctx->app->close(EXIT_SUCCESS); }; - auto menu_back_callback = [ctx]() - { - application::state next_state; - next_state.name = "title"; - next_state.enter = std::bind(game::state::title::enter, ctx); - next_state.exit = std::bind(game::state::title::exit, ctx); - ctx->app->change_state(next_state); - }; // Build list of menu select callbacks ctx->menu_select_callbacks.push_back(select_start_callback); @@ -146,7 +154,7 @@ void enter(game::context* ctx) ctx->menu_right_callbacks.push_back(nullptr); // Set menu back callback - ctx->menu_back_callback = menu_back_callback; + ctx->menu_back_callback = select_quit_callback; // Schedule menu control setup timeline* timeline = ctx->timeline; @@ -162,6 +170,11 @@ void exit(game::context* ctx) game::menu::remove_text_from_ui(ctx); game::menu::delete_text(ctx); + // Destruct title text + ctx->ui_scene->remove_object(ctx->title_text); + delete ctx->title_text; + ctx->title_text = nullptr; + ctx->ui_clear_pass->set_cleared_buffers(false, true, false); } diff --git a/src/game/states/options-menu.cpp b/src/game/states/options-menu.cpp index 167ba74..907b697 100644 --- a/src/game/states/options-menu.cpp +++ b/src/game/states/options-menu.cpp @@ -67,7 +67,7 @@ void enter(game::context* ctx) game::menu::update_text_color(ctx); game::menu::update_text_font(ctx); - game::menu::align_text(ctx); + game::menu::align_text(ctx, true); game::menu::update_text_tweens(ctx); game::menu::add_text_to_ui(ctx); diff --git a/src/game/states/splash.cpp b/src/game/states/splash.cpp index 4ada7a5..07acbcc 100644 --- a/src/game/states/splash.cpp +++ b/src/game/states/splash.cpp @@ -18,7 +18,7 @@ */ #include "game/states/splash.hpp" -#include "game/states/title.hpp" +#include "game/states/main-menu.hpp" #include "animation/screen-transition.hpp" #include "animation/animation.hpp" #include "animation/animator.hpp" @@ -94,9 +94,9 @@ void enter(game::context* ctx) [ctx]() { application::state next_state; - next_state.name = "title"; - next_state.enter = std::bind(game::state::title::enter, ctx); - next_state.exit = std::bind(game::state::title::exit, ctx); + next_state.name = "main_menu"; + 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); } ); @@ -123,9 +123,9 @@ void enter(game::context* ctx) // Change state application::state next_state; - next_state.name = "title"; - next_state.enter = std::bind(game::state::title::enter, ctx); - next_state.exit = std::bind(game::state::title::exit, ctx); + next_state.name = "main_menu"; + 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/states.hpp b/src/game/states/states.hpp deleted file mode 100644 index 62f785b..0000000 --- a/src/game/states/states.hpp +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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_STATES_HPP -#define ANTKEEPER_GAME_STATES_HPP - -namespace game { - -/// Game state functions. -namespace state {} - -} // namespace game - -#include "game/states/boot.hpp" -#include "game/states/loading.hpp" -#include "game/states/splash.hpp" -#include "game/states/title.hpp" -#include "game/states/main-menu.hpp" - -#endif // ANTKEEPER_GAME_STATES_HPP diff --git a/src/game/states/title.cpp b/src/game/states/title.cpp deleted file mode 100644 index adc35aa..0000000 --- a/src/game/states/title.cpp +++ /dev/null @@ -1,223 +0,0 @@ -/* - * 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/title.hpp" -#include "game/states/main-menu.hpp" -#include "animation/screen-transition.hpp" -#include "animation/ease.hpp" -#include "animation/animation.hpp" -#include "animation/animator.hpp" -#include "application.hpp" -#include "scene/text.hpp" -#include "configuration.hpp" -#include "render/passes/clear-pass.hpp" - -namespace game { -namespace state { -namespace title { - -void enter(game::context* ctx) -{ - ctx->ui_clear_pass->set_cleared_buffers(true, true, false); - - // Construct title text - ctx->title_text = new scene::text(); - ctx->title_text->set_material(&ctx->title_font_material); - ctx->title_text->set_font(&ctx->title_font); - ctx->title_text->set_color({1.0f, 1.0f, 1.0f, 0.0f}); - ctx->title_text->set_content((*ctx->strings)["title_antkeeper"]); - - // Construct "Press any key" text - ctx->title_press_any_key_text = new scene::text(); - ctx->title_press_any_key_text->set_material(&ctx->menu_font_material); - ctx->title_press_any_key_text->set_font(&ctx->menu_font); - ctx->title_press_any_key_text->set_color({1.0f, 1.0f, 1.0f, 0.0f}); - ctx->title_press_any_key_text->set_content((*ctx->strings)["title_press_any_key"]); - - int window_height = std::get<1>(ctx->app->get_viewport_dimensions()); - - // Align title text - const auto& title_aabb = static_cast&>(ctx->title_text->get_local_bounds()); - float title_w = title_aabb.max_point.x - title_aabb.min_point.x; - float title_h = title_aabb.max_point.y - title_aabb.min_point.y; - ctx->title_text->set_translation({std::round(-title_w * 0.5f), std::round(-title_h * 0.5f + (window_height / 3) / 2), 0.0f}); - - // Align "Press any key" text - const auto& any_key_aabb = static_cast&>(ctx->title_press_any_key_text->get_local_bounds()); - float any_key_w = any_key_aabb.max_point.x - any_key_aabb.min_point.x; - float any_key_h = any_key_aabb.max_point.y - any_key_aabb.min_point.y; - ctx->title_press_any_key_text->set_translation({std::round(-any_key_w * 0.5f), std::round(-any_key_h * 0.5f - (window_height / 3) / 2), 0.0f}); - - // Load animation timing configuration - double title_fade_in_duration = 0.0; - double title_fade_out_duration = 0.0; - double title_press_any_key_duration = 0.0; - double title_press_any_key_delay = 0.0; - if (ctx->config->contains("title_fade_in_duration")) - title_fade_in_duration = (*ctx->config)["title_fade_in_duration"].get(); - if (ctx->config->contains("title_fade_out_duration")) - title_fade_out_duration = (*ctx->config)["title_fade_out_duration"].get(); - if (ctx->config->contains("title_press_any_key_duration")) - title_press_any_key_duration = (*ctx->config)["title_press_any_key_duration"].get(); - if (ctx->config->contains("title_press_any_key_delay")) - title_press_any_key_delay = (*ctx->config)["title_press_any_key_delay"].get(); - - auto set_title_opacity = [ctx](int channel, const float& opacity) - { - ctx->title_text->set_color({1.0f, 1.0f, 1.0f, opacity}); - }; - - // Build title fade in animation - ctx->title_fade_in_animation = new animation(); - animation_channel* title_fade_in_opacity_channel = ctx->title_fade_in_animation->add_channel(0); - ctx->title_fade_in_animation->set_interpolator(ease::in_quad); - title_fade_in_opacity_channel->insert_keyframe({0.0, 0.0f}); - title_fade_in_opacity_channel->insert_keyframe({title_fade_in_duration, 1.0f}); - title_fade_in_opacity_channel->insert_keyframe({title_fade_in_duration + title_press_any_key_delay, 1.0f}); - ctx->title_fade_in_animation->set_frame_callback(set_title_opacity); - - // Trigger "Press any key" animation after title fade in animation ends - ctx->title_fade_in_animation->set_end_callback - ( - [ctx]() - { - ctx->title_press_any_key_animation->play(); - } - ); - - // Build title fade out animation - ctx->title_fade_out_animation = new animation(); - animation_channel* title_fade_out_opacity_channel = ctx->title_fade_out_animation->add_channel(0); - ctx->title_fade_out_animation->set_interpolator(ease::out_quad); - title_fade_out_opacity_channel->insert_keyframe({0.0, 1.0f}); - title_fade_out_opacity_channel->insert_keyframe({title_fade_out_duration, 0.0f}); - ctx->title_fade_out_animation->set_frame_callback(set_title_opacity); - - // Trigger a state change when the title fade out animation ends - ctx->title_fade_out_animation->set_end_callback - ( - [ctx]() - { - application::state next_state; - next_state.name = "main_menu"; - 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); - } - ); - - // Build "Press any key" animation - ctx->title_press_any_key_animation = new animation(); - ctx->title_press_any_key_animation->loop(true); - animation_channel* title_press_any_key_opacity_channel = ctx->title_press_any_key_animation->add_channel(0); - ctx->title_press_any_key_animation->set_interpolator(math::lerp); - title_press_any_key_opacity_channel->insert_keyframe({0.0, 0.0f}); - title_press_any_key_opacity_channel->insert_keyframe({title_press_any_key_duration * 0.5, 1.0f}); - title_press_any_key_opacity_channel->insert_keyframe({title_press_any_key_duration, 0.0f}); - ctx->title_press_any_key_animation->set_frame_callback - ( - [ctx](int channel, const float& opacity) - { - ctx->title_press_any_key_text->set_color({1.0f, 1.0f, 1.0f, 0.5f * ease::out_cubic(0.0f, 1.0f, opacity)}); - } - ); - - // Add title fade animations to animator - ctx->animator->add_animation(ctx->title_fade_in_animation); - ctx->animator->add_animation(ctx->title_fade_out_animation); - ctx->animator->add_animation(ctx->title_press_any_key_animation); - - // Start title fade in animation - ctx->title_fade_in_animation->play(); - - // Set up title skipper - ctx->input_listener->set_callback - ( - [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) - { - /* - if (ctx->title_fade_in_animation->is_stopped()) - { - ctx->title_fade_out_animation->play(); - ctx->input_listener->set_enabled(false); - } - */ - - if (ctx->title_text->get_color()[3] > 0.0f) - { - ctx->input_listener->set_enabled(false); - - // Black out screen - ctx->rasterizer->set_clear_color(0.0f, 0.0f, 0.0f, 1.0f); - ctx->rasterizer->clear_framebuffer(true, false, false); - ctx->app->swap_buffers(); - - // Change state - application::state next_state; - next_state.name = "main_menu"; - 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); - } - } - } - ); - ctx->input_listener->set_enabled(true); - - ctx->ui_scene->add_object(ctx->title_text); - ctx->title_text->update_tweens(); - - ctx->ui_scene->add_object(ctx->title_press_any_key_text); - ctx->title_press_any_key_text->update_tweens(); -} - -void exit(game::context* ctx) -{ - // Remove title text - ctx->ui_scene->remove_object(ctx->title_text); - ctx->ui_scene->remove_object(ctx->title_press_any_key_text); - - // Disable title skipper - ctx->input_listener->set_enabled(false); - ctx->input_listener->set_callback(nullptr); - - // Destruct title animations - ctx->animator->remove_animation(ctx->title_fade_in_animation); - ctx->animator->remove_animation(ctx->title_fade_out_animation); - ctx->animator->remove_animation(ctx->title_press_any_key_animation); - - delete ctx->title_fade_in_animation; - delete ctx->title_fade_out_animation; - delete ctx->title_press_any_key_animation; - ctx->title_fade_in_animation = nullptr; - ctx->title_fade_out_animation = nullptr; - ctx->title_press_any_key_animation = nullptr; - - ctx->ui_clear_pass->set_cleared_buffers(false, true, false); -} - -} // namespace title -} // namespace state -} // namespace game diff --git a/src/game/states/title.hpp b/src/game/states/title.hpp deleted file mode 100644 index 90acecf..0000000 --- a/src/game/states/title.hpp +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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_TITLE_HPP -#define ANTKEEPER_GAME_STATE_TITLE_HPP - -#include "game/context.hpp" - -namespace game { -namespace state { - -/// Title screen game state functions. -namespace title { - -void enter(game::context* ctx); -void exit(game::context* ctx); - -} // namespace title - -} // namespace state -} // namespace game - -#endif // ANTKEEPER_GAME_STATE_TITLE_HPP diff --git a/src/type/font-metrics.hpp b/src/type/font-metrics.hpp index 3ee8466..4cf7a59 100644 --- a/src/type/font-metrics.hpp +++ b/src/type/font-metrics.hpp @@ -27,6 +27,9 @@ namespace type { */ struct font_metrics { + /// Vertical size of the font, in pixels. + float size; + /// Positive distance from the baseline to the highest or upper grid coordinate. float ascent; diff --git a/src/type/freetype/typeface.cpp b/src/type/freetype/typeface.cpp index 4a48252..48d7d2d 100644 --- a/src/type/freetype/typeface.cpp +++ b/src/type/freetype/typeface.cpp @@ -54,6 +54,7 @@ bool typeface::get_metrics(float height, font_metrics& metrics) const set_face_pixel_size(height); // Get font metrics + metrics.size = height; metrics.ascent = face->size->metrics.ascender / 64.0f; metrics.descent = face->size->metrics.descender / 64.0f; metrics.linespace = face->size->metrics.height / 64.0f;