Browse Source

Improve menu text alignment. Merge title state into main menu state

master
C. J. Howard 1 year ago
parent
commit
446c7e1921
13 changed files with 84 additions and 323 deletions
  1. +1
    -0
      CMakeLists.txt
  2. +32
    -4
      src/game/menu.cpp
  3. +4
    -1
      src/game/menu.hpp
  4. +8
    -0
      src/game/states/gamepad-config-menu.cpp
  5. +3
    -1
      src/game/states/keyboard-config-menu.cpp
  6. +24
    -11
      src/game/states/main-menu.cpp
  7. +1
    -1
      src/game/states/options-menu.cpp
  8. +7
    -7
      src/game/states/splash.cpp
  9. +0
    -36
      src/game/states/states.hpp
  10. +0
    -223
      src/game/states/title.cpp
  11. +0
    -39
      src/game/states/title.hpp
  12. +3
    -0
      src/type/font-metrics.hpp
  13. +1
    -0
      src/type/freetype/typeface.cpp

+ 1
- 0
CMakeLists.txt View File

@ -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)

+ 32
- 4
src/game/menu.cpp View File

@ -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<const geom::aabb<float>&>(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<const geom::aabb<float>&>(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<float>(event.x - viewport[0] / 2);
const float y = static_cast<float>((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)

+ 4
- 1
src/game/menu.hpp View File

@ -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);

+ 8
- 0
src/game/states/gamepad-config-menu.cpp View File

@ -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<const gamepad_button_pressed_event&>(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<const key_pressed_event&>(event);
if (key_event.scancode != input::scancode::escape && key_event.scancode != input::scancode::backspace)
return;
}
else
{
return;

+ 3
- 1
src/game/states/keyboard-config-menu.cpp View File

@ -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<const key_pressed_event&>(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)
{

+ 24
- 11
src/game/states/main-menu.cpp View File

@ -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<const geom::aabb<float>&>(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);
}

+ 1
- 1
src/game/states/options-menu.cpp View File

@ -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);

+ 7
- 7
src/game/states/splash.cpp View File

@ -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);
}
}

+ 0
- 36
src/game/states/states.hpp View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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

+ 0
- 223
src/game/states/title.cpp View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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<const geom::aabb<float>&>(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<const geom::aabb<float>&>(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<double>();
if (ctx->config->contains("title_fade_out_duration"))
title_fade_out_duration = (*ctx->config)["title_fade_out_duration"].get<double>();
if (ctx->config->contains("title_press_any_key_duration"))
title_press_any_key_duration = (*ctx->config)["title_press_any_key_duration"].get<double>();
if (ctx->config->contains("title_press_any_key_delay"))
title_press_any_key_delay = (*ctx->config)["title_press_any_key_delay"].get<double>();
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<float>();
animation_channel<float>* title_fade_in_opacity_channel = ctx->title_fade_in_animation->add_channel(0);
ctx->title_fade_in_animation->set_interpolator(ease<float>::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<float>();
animation_channel<float>* title_fade_out_opacity_channel = ctx->title_fade_out_animation->add_channel(0);
ctx->title_fade_out_animation->set_interpolator(ease<float>::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<float>();
ctx->title_press_any_key_animation->loop(true);
animation_channel<float>* 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<float, double>);
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<float>::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

+ 0
- 39
src/game/states/title.hpp View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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

+ 3
- 0
src/type/font-metrics.hpp View File

@ -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;

+ 1
- 0
src/type/freetype/typeface.cpp View File

@ -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;

Loading…
Cancel
Save