@ -0,0 +1,178 @@ | |||||
/* | |||||
* 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/fonts.hpp" | |||||
#include "application.hpp" | |||||
#include "type/type.hpp" | |||||
#include "resources/resource-manager.hpp" | |||||
#include "gl/texture-wrapping.hpp" | |||||
#include "gl/texture-filter.hpp" | |||||
#include "render/material.hpp" | |||||
#include "render/material-flags.hpp" | |||||
#include <codecvt> | |||||
namespace game { | |||||
static void build_bitmap_font(const type::typeface& typeface, float size, const std::unordered_set<char32_t>& charset, type::bitmap_font& font, render::material& material, gl::shader_program* shader_program) | |||||
{ | |||||
// Get font metrics for given size | |||||
if (type::font_metrics metrics; typeface.get_metrics(size, metrics)) | |||||
font.set_font_metrics(metrics); | |||||
// Format font bitmap | |||||
image& font_bitmap = font.get_bitmap(); | |||||
font_bitmap.format(sizeof(std::byte), 1); | |||||
// For each UTF-32 character code in the character set | |||||
for (char32_t code: charset) | |||||
{ | |||||
// Skip missing glyphs | |||||
if (!typeface.has_glyph(code)) | |||||
continue; | |||||
// Add glyph to font | |||||
type::bitmap_glyph& glyph = font[code]; | |||||
typeface.get_metrics(size, code, glyph.metrics); | |||||
typeface.get_bitmap(size, code, glyph.bitmap); | |||||
} | |||||
// Pack glyph bitmaps into the font bitmap | |||||
font.pack(); | |||||
// Create font texture from bitmap | |||||
gl::texture_2d* font_texture = new gl::texture_2d(font_bitmap.get_width(), font_bitmap.get_height(), gl::pixel_type::uint_8, gl::pixel_format::r, gl::color_space::linear, font_bitmap.get_pixels()); | |||||
font_texture->set_wrapping(gl::texture_wrapping::extend, gl::texture_wrapping::extend); | |||||
font_texture->set_filters(gl::texture_min_filter::linear, gl::texture_mag_filter::linear); | |||||
// Create font material | |||||
material.set_flags(MATERIAL_FLAG_TRANSLUCENT); | |||||
material.add_property<const gl::texture_2d*>("font_bitmap")->set_value(font_texture); | |||||
material.set_shader_program(shader_program); | |||||
} | |||||
void load_fonts(game::context* ctx) | |||||
{ | |||||
// Load dyslexia-friendly typeface (if enabled) | |||||
bool dyslexia_font = false; | |||||
if (ctx->config->contains("dyslexia_font")) | |||||
{ | |||||
dyslexia_font = (*ctx->config)["dyslexia_font"].get<bool>(); | |||||
if (dyslexia_font) | |||||
{ | |||||
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<type::typeface>(it->second); | |||||
} | |||||
else | |||||
{ | |||||
dyslexia_font = false; | |||||
} | |||||
} | |||||
} | |||||
// Load typefaces | |||||
if (dyslexia_font) | |||||
{ | |||||
// Override standard typefaces with dyslexia-friendly typeface | |||||
ctx->typefaces["serif"] = ctx->typefaces["dyslexia"]; | |||||
ctx->typefaces["sans_serif"] = ctx->typefaces["dyslexia"]; | |||||
ctx->typefaces["monospace"] = ctx->typefaces["dyslexia"]; | |||||
} | |||||
else | |||||
{ | |||||
// Load standard typefaces | |||||
if (auto it = ctx->strings->find("font_serif"); it != ctx->strings->end()) | |||||
ctx->typefaces["serif"] = ctx->resource_manager->load<type::typeface>(it->second); | |||||
if (auto it = ctx->strings->find("font_sans_serif"); it != ctx->strings->end()) | |||||
ctx->typefaces["sans_serif"] = ctx->resource_manager->load<type::typeface>(it->second); | |||||
if (auto it = ctx->strings->find("font_monospace"); it != ctx->strings->end()) | |||||
ctx->typefaces["monospace"] = ctx->resource_manager->load<type::typeface>(it->second); | |||||
} | |||||
// Build character set | |||||
std::unordered_set<char32_t> charset; | |||||
{ | |||||
// Add all character codes from the basic Latin unicode block | |||||
for (char32_t code = type::unicode::block::basic_latin.first; code <= type::unicode::block::basic_latin.last; ++code) | |||||
charset.insert(code); | |||||
// Add all character codes from game strings | |||||
for (auto it = ctx->strings->begin(); it != ctx->strings->end(); ++it) | |||||
{ | |||||
// Convert UTF-8 string to UTF-32 | |||||
std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> convert; | |||||
std::u32string u32 = convert.from_bytes(it->second); | |||||
/// Insert each character code from the UTF-32 string into the character set | |||||
for (char32_t code: u32) | |||||
charset.insert(code); | |||||
} | |||||
} | |||||
// Load bitmap font shader | |||||
gl::shader_program* bitmap_font_shader = ctx->resource_manager->load<gl::shader_program>("bitmap-font.glsl"); | |||||
// Determine font point sizes | |||||
float debug_font_size_pt = 12.0f; | |||||
float menu_font_size_pt = 12.0f; | |||||
float title_font_size_pt = 12.0f; | |||||
if (ctx->config->contains("debug_font_size")) | |||||
debug_font_size_pt = (*ctx->config)["debug_font_size"].get<float>(); | |||||
if (ctx->config->contains("menu_font_size")) | |||||
menu_font_size_pt = (*ctx->config)["menu_font_size"].get<float>(); | |||||
if (ctx->config->contains("title_font_size")) | |||||
title_font_size_pt = (*ctx->config)["title_font_size"].get<float>(); | |||||
// Scale font point sizes | |||||
if (ctx->config->contains("font_scale")) | |||||
{ | |||||
const float font_scale = (*ctx->config)["font_scale"].get<float>(); | |||||
debug_font_size_pt *= font_scale; | |||||
menu_font_size_pt *= font_scale; | |||||
title_font_size_pt *= font_scale; | |||||
} | |||||
// Convert font point sizes to pixel sizes | |||||
const float dpi = ctx->app->get_display_dpi(); | |||||
const float debug_font_size_px = (debug_font_size_pt * dpi) / 72.0f; | |||||
const float menu_font_size_px = (menu_font_size_pt * dpi) / 72.0f; | |||||
const float title_font_size_px = (title_font_size_pt * dpi) / 72.0f; | |||||
// Build debug font | |||||
if (auto it = ctx->typefaces.find("monospace"); it != ctx->typefaces.end()) | |||||
{ | |||||
build_bitmap_font(*it->second, debug_font_size_px, charset, ctx->debug_font, ctx->debug_font_material, bitmap_font_shader); | |||||
} | |||||
// Build menu font | |||||
if (auto it = ctx->typefaces.find("sans_serif"); it != ctx->typefaces.end()) | |||||
{ | |||||
build_bitmap_font(*it->second, menu_font_size_px, charset, ctx->menu_font, ctx->menu_font_material, bitmap_font_shader); | |||||
} | |||||
// Build title font | |||||
if (auto it = ctx->typefaces.find("serif"); it != ctx->typefaces.end()) | |||||
{ | |||||
build_bitmap_font(*it->second, title_font_size_px, charset, ctx->title_font, ctx->title_font_material, bitmap_font_shader); | |||||
} | |||||
} | |||||
} // namespace game |
@ -0,0 +1,31 @@ | |||||
/* | |||||
* 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_FONTS_HPP | |||||
#define ANTKEEPER_GAME_FONTS_HPP | |||||
#include "game/context.hpp" | |||||
namespace game { | |||||
void load_fonts(game::context* ctx); | |||||
} // namespace game | |||||
#endif // ANTKEEPER_GAME_FONTS_HPP |
@ -0,0 +1,142 @@ | |||||
/* | |||||
* 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/credits.hpp" | |||||
#include "game/states/main-menu.hpp" | |||||
#include "animation/ease.hpp" | |||||
#include "animation/animation.hpp" | |||||
#include "animation/animator.hpp" | |||||
#include "application.hpp" | |||||
#include "scene/text.hpp" | |||||
#include "render/passes/clear-pass.hpp" | |||||
namespace game { | |||||
namespace state { | |||||
namespace credits { | |||||
void enter(game::context* ctx) | |||||
{ | |||||
ctx->ui_clear_pass->set_cleared_buffers(true, true, false); | |||||
// Construct credits text | |||||
ctx->credits_text = new scene::text(); | |||||
ctx->credits_text->set_material(&ctx->menu_font_material); | |||||
ctx->credits_text->set_font(&ctx->menu_font); | |||||
ctx->credits_text->set_color({1.0f, 1.0f, 1.0f, 0.0f}); | |||||
ctx->credits_text->set_content((*ctx->strings)["credits"]); | |||||
// Align credits text | |||||
const auto& credits_aabb = static_cast<const geom::aabb<float>&>(ctx->credits_text->get_local_bounds()); | |||||
float credits_w = credits_aabb.max_point.x - credits_aabb.min_point.x; | |||||
float credits_h = credits_aabb.max_point.y - credits_aabb.min_point.y; | |||||
ctx->credits_text->set_translation({std::round(-credits_w * 0.5f), std::round(-credits_h * 0.5f), 0.0f}); | |||||
// Load animation timing configuration | |||||
double credits_fade_in_duration = 0.0; | |||||
double credits_scroll_duration = 0.0; | |||||
if (ctx->config->contains("credits_fade_in_duration")) | |||||
credits_fade_in_duration = (*ctx->config)["credits_fade_in_duration"].get<double>(); | |||||
if (ctx->config->contains("credits_scroll_duration")) | |||||
credits_scroll_duration = (*ctx->config)["credits_scroll_duration"].get<double>(); | |||||
auto set_credits_opacity = [ctx](int channel, const float& opacity) | |||||
{ | |||||
ctx->credits_text->set_color({1.0f, 1.0f, 1.0f, opacity}); | |||||
}; | |||||
// Build credits fade in animation | |||||
ctx->credits_fade_in_animation = new animation<float>(); | |||||
animation_channel<float>* credits_fade_in_opacity_channel = ctx->credits_fade_in_animation->add_channel(0); | |||||
ctx->credits_fade_in_animation->set_interpolator(ease<float>::in_quad); | |||||
credits_fade_in_opacity_channel->insert_keyframe({0.0, 0.0f}); | |||||
credits_fade_in_opacity_channel->insert_keyframe({credits_fade_in_duration, 1.0f}); | |||||
ctx->credits_fade_in_animation->set_frame_callback(set_credits_opacity); | |||||
// Build credits scroll in animation | |||||
ctx->credits_scroll_animation = new animation<float>(); | |||||
// Trigger credits scroll animation after credits fade in animation ends | |||||
ctx->credits_fade_in_animation->set_end_callback | |||||
( | |||||
[ctx]() | |||||
{ | |||||
ctx->credits_scroll_animation->play(); | |||||
} | |||||
); | |||||
// Add credits animations to animator | |||||
ctx->animator->add_animation(ctx->credits_fade_in_animation); | |||||
ctx->animator->add_animation(ctx->credits_scroll_animation); | |||||
// Start credits fade in animation | |||||
ctx->credits_fade_in_animation->play(); | |||||
// Set up credits skipper | |||||
ctx->input_listener->set_callback | |||||
( | |||||
[ctx](const event_base& event) | |||||
{ | |||||
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->credits_text->get_color()[3] > 0.0f) | |||||
{ | |||||
ctx->input_listener->set_enabled(false); | |||||
// 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.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->credits_text); | |||||
ctx->credits_text->update_tweens(); | |||||
} | |||||
void exit(game::context* ctx) | |||||
{ | |||||
// Disable credits skipper | |||||
ctx->input_listener->set_enabled(false); | |||||
ctx->input_listener->set_callback(nullptr); | |||||
// Destruct credits text | |||||
ctx->ui_scene->remove_object(ctx->credits_text); | |||||
delete ctx->credits_text; | |||||
ctx->credits_text = nullptr; | |||||
// Destruct credits animations | |||||
ctx->animator->remove_animation(ctx->credits_fade_in_animation); | |||||
ctx->animator->remove_animation(ctx->credits_scroll_animation); | |||||
delete ctx->credits_fade_in_animation; | |||||
delete ctx->credits_scroll_animation; | |||||
ctx->credits_fade_in_animation = nullptr; | |||||
ctx->credits_scroll_animation = nullptr; | |||||
ctx->ui_clear_pass->set_cleared_buffers(false, true, false); | |||||
} | |||||
} // namespace credits | |||||
} // namespace state | |||||
} // namespace game |
@ -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 <http://www.gnu.org/licenses/>. | |||||
*/ | |||||
#ifndef ANTKEEPER_GAME_STATE_CREDITS_HPP | |||||
#define ANTKEEPER_GAME_STATE_CREDITS_HPP | |||||
#include "game/context.hpp" | |||||
namespace game { | |||||
namespace state { | |||||
/// Credits screen game state functions. | |||||
namespace credits { | |||||
void enter(game::context* ctx); | |||||
void exit(game::context* ctx); | |||||
} // namespace credits | |||||
} // namespace state | |||||
} // namespace game | |||||
#endif // ANTKEEPER_GAME_STATE_CREDITS_HPP |
@ -0,0 +1,279 @@ | |||||
/* | |||||
* 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/language-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 language_menu { | |||||
static void update_text_font(game::context* ctx) | |||||
{ | |||||
for (scene::text* text: ctx->language_menu_texts) | |||||
{ | |||||
text->set_material(&ctx->menu_font_material); | |||||
text->set_font(&ctx->menu_font); | |||||
} | |||||
} | |||||
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->language_menu_texts.size(); ++i) | |||||
{ | |||||
scene::text* text = ctx->language_menu_texts[i]; | |||||
if (i == ctx->language_menu_index) | |||||
text->set_color(active_color); | |||||
else | |||||
text->set_color(inactive_color); | |||||
} | |||||
} | |||||
static void update_text_content(game::context* ctx) | |||||
{ | |||||
ctx->language_menu_language_text->set_content((*ctx->strings)["language_name"]); | |||||
ctx->language_menu_back_text->set_content((*ctx->strings)["back"]); | |||||
} | |||||
static void refresh_texts(game::context* ctx) | |||||
{ | |||||
for (scene::text* text: ctx->language_menu_texts) | |||||
{ | |||||
text->refresh(); | |||||
} | |||||
} | |||||
static void align_texts(game::context* ctx) | |||||
{ | |||||
float menu_width = 0.0f; | |||||
for (std::size_t i = 0; i < ctx->language_menu_texts.size(); ++i) | |||||
{ | |||||
scene::text* text = ctx->language_menu_texts[i]; | |||||
// Update menu width | |||||
const auto& bounds = static_cast<const geom::aabb<float>&>(text->get_local_bounds()); | |||||
float width = bounds.max_point.x - bounds.min_point.x; | |||||
menu_width = std::max<float>(menu_width, width); | |||||
} | |||||
float menu_height = ctx->language_menu_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->language_menu_texts.size(); ++i) | |||||
{ | |||||
scene::text* text = ctx->language_menu_texts[i]; | |||||
// Align text | |||||
const auto& bounds = static_cast<const geom::aabb<float>&>(text->get_local_bounds()); | |||||
float w = bounds.max_point.x - bounds.min_point.x; | |||||
float x = -w * 0.5f; | |||||
//float x = menu_x; | |||||
float y = menu_y - ctx->menu_font.get_font_metrics().linespace * i; | |||||
text->set_translation({std::round(x), std::round(y), 0.0f}); | |||||
} | |||||
} | |||||
static void update_text_tweens(game::context* ctx) | |||||
{ | |||||
for (scene::text* text: ctx->language_menu_texts) | |||||
{ | |||||
text->update_tweens(); | |||||
} | |||||
} | |||||
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(); | |||||
// Build list of language menu texts | |||||
ctx->language_menu_texts.push_back(ctx->language_menu_language_text); | |||||
ctx->language_menu_texts.push_back(ctx->language_menu_back_text); | |||||
// Construct language 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); | |||||
}; | |||||
auto next_language_callback = [ctx]() | |||||
{ | |||||
if (ctx->language_menu_index != 0) | |||||
return; | |||||
// Increment language index | |||||
++ctx->language_index; | |||||
if (ctx->language_index >= ctx->language_count) | |||||
ctx->language_index = 0; | |||||
// Find corresponding language code and strings | |||||
ctx->language_code = (*ctx->string_table)[0][ctx->language_index + 2]; | |||||
ctx->strings = &ctx->string_table_map[ctx->language_code]; | |||||
// Update language in config | |||||
(*ctx->config)["language"] = ctx->language_code; | |||||
ctx->logger->log("Language changed to \"" + ctx->language_code + "\""); | |||||
// 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); | |||||
update_text_font(ctx); | |||||
update_text_content(ctx); | |||||
refresh_texts(ctx); | |||||
align_texts(ctx); | |||||
update_text_tweens(ctx); | |||||
}; | |||||
auto previous_language_callback = [ctx]() | |||||
{ | |||||
if (ctx->language_menu_index != 0) | |||||
return; | |||||
// Increment language index | |||||
--ctx->language_index; | |||||
if (ctx->language_index < 0) | |||||
ctx->language_index = ctx->language_count - 1; | |||||
// Find corresponding language code and strings | |||||
ctx->language_code = (*ctx->string_table)[0][ctx->language_index + 2]; | |||||
ctx->strings = &ctx->string_table_map[ctx->language_code]; | |||||
ctx->logger->log("Language changed to \"" + ctx->language_code + "\""); | |||||
// 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); | |||||
update_text_font(ctx); | |||||
update_text_content(ctx); | |||||
refresh_texts(ctx); | |||||
align_texts(ctx); | |||||
update_text_tweens(ctx); | |||||
}; | |||||
// Build list of language menu callbacks | |||||
ctx->language_menu_callbacks.push_back(next_language_callback); | |||||
ctx->language_menu_callbacks.push_back(menu_back_callback); | |||||
ctx->controls["menu_down"]->set_activated_callback | |||||
( | |||||
[ctx]() | |||||
{ | |||||
++ctx->language_menu_index; | |||||
if (ctx->language_menu_index >= ctx->language_menu_texts.size()) | |||||
ctx->language_menu_index = 0; | |||||
update_text_color(ctx); | |||||
} | |||||
); | |||||
ctx->controls["menu_up"]->set_activated_callback | |||||
( | |||||
[ctx]() | |||||
{ | |||||
--ctx->language_menu_index; | |||||
if (ctx->language_menu_index < 0) | |||||
ctx->language_menu_index = ctx->language_menu_texts.size() - 1; | |||||
update_text_color(ctx); | |||||
} | |||||
); | |||||
ctx->controls["menu_left"]->set_activated_callback(previous_language_callback); | |||||
ctx->controls["menu_right"]->set_activated_callback(next_language_callback); | |||||
ctx->controls["menu_select"]->set_activated_callback | |||||
( | |||||
[ctx]() | |||||
{ | |||||
auto callback = ctx->language_menu_callbacks[ctx->language_menu_index]; | |||||
if (callback != nullptr) | |||||
callback(); | |||||
} | |||||
); | |||||
ctx->controls["menu_back"]->set_activated_callback(menu_back_callback); | |||||
for (scene::text* text: ctx->language_menu_texts) | |||||
ctx->ui_scene->add_object(text); | |||||
update_text_font(ctx); | |||||
update_text_color(ctx); | |||||
update_text_content(ctx); | |||||
align_texts(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 language menu callbacks | |||||
ctx->language_menu_callbacks.clear(); | |||||
// Destruct language menu texts | |||||
for (scene::text* text: ctx->language_menu_texts) | |||||
{ | |||||
ctx->ui_scene->remove_object(text); | |||||
delete text; | |||||
} | |||||
ctx->language_menu_texts.clear(); | |||||
ctx->ui_clear_pass->set_cleared_buffers(false, true, false); | |||||
} | |||||
} // namespace language_menu | |||||
} // namespace state | |||||
} // namespace game |
@ -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 <http://www.gnu.org/licenses/>. | |||||
*/ | |||||
#ifndef ANTKEEPER_GAME_STATE_LANGUAGE_MENU_HPP | |||||
#define ANTKEEPER_GAME_STATE_LANGUAGE_MENU_HPP | |||||
#include "game/context.hpp" | |||||
namespace game { | |||||
namespace state { | |||||
/// Language menu screen game state functions. | |||||
namespace language_menu { | |||||
void enter(game::context* ctx); | |||||
void exit(game::context* ctx); | |||||
} // namespace language_menu | |||||
} // namespace state | |||||
} // namespace game | |||||
#endif // ANTKEEPER_GAME_STATE_LANGUAGE_MENU_HPP |
@ -0,0 +1,213 @@ | |||||
/* | |||||
* 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/options-menu.hpp" | |||||
#include "game/states/main-menu.hpp" | |||||
#include "game/states/language-menu.hpp" | |||||
#include "animation/ease.hpp" | |||||
#include "animation/animation.hpp" | |||||
#include "animation/animator.hpp" | |||||
#include "application.hpp" | |||||
#include "scene/text.hpp" | |||||
#include "render/passes/clear-pass.hpp" | |||||
namespace game { | |||||
namespace state { | |||||
namespace options_menu { | |||||
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(); | |||||
ctx->options_menu_sound_text = new scene::text(); | |||||
ctx->options_menu_language_text = new scene::text(); | |||||
ctx->options_menu_back_text = new scene::text(); | |||||
// Build list of options menu texts | |||||
ctx->options_menu_texts.push_back(ctx->options_menu_controls_text); | |||||
ctx->options_menu_texts.push_back(ctx->options_menu_graphics_text); | |||||
ctx->options_menu_texts.push_back(ctx->options_menu_sound_text); | |||||
ctx->options_menu_texts.push_back(ctx->options_menu_language_text); | |||||
ctx->options_menu_texts.push_back(ctx->options_menu_back_text); | |||||
// Construct options menu callbacks | |||||
auto menu_back_callback = [ctx]() | |||||
{ | |||||
application::state next_state; | |||||
next_state.name = "main_menu"; | |||||
next_state.enter = std::bind(game::state::main_menu::enter, ctx, 1); | |||||
next_state.exit = std::bind(game::state::main_menu::exit, ctx); | |||||
ctx->app->change_state(next_state); | |||||
}; | |||||
auto change_state_language_menu = [ctx]() | |||||
{ | |||||
application::state next_state; | |||||
next_state.name = "language_menu"; | |||||
next_state.enter = std::bind(game::state::language_menu::enter, ctx); | |||||
next_state.exit = std::bind(game::state::language_menu::exit, ctx); | |||||
ctx->app->change_state(next_state); | |||||
}; | |||||
// 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(nullptr); | |||||
ctx->options_menu_callbacks.push_back(change_state_language_menu); | |||||
ctx->options_menu_callbacks.push_back(menu_back_callback); | |||||
// Set content of texts | |||||
ctx->options_menu_controls_text->set_content((*ctx->strings)["options_menu_controls"]); | |||||
ctx->options_menu_graphics_text->set_content((*ctx->strings)["options_menu_graphics"]); | |||||
ctx->options_menu_sound_text->set_content((*ctx->strings)["options_menu_sound"]); | |||||
ctx->options_menu_language_text->set_content((*ctx->strings)["options_menu_language"]); | |||||
ctx->options_menu_back_text->set_content((*ctx->strings)["back"]); | |||||
float4 inactive_color = {1.0f, 1.0f, 1.0f, 0.5f}; | |||||
float4 active_color = {1.0f, 1.0f, 1.0f, 1.0f}; | |||||
float menu_width = 0.0f; | |||||
for (std::size_t i = 0; i < ctx->options_menu_texts.size(); ++i) | |||||
{ | |||||
scene::text* text = ctx->options_menu_texts[i]; | |||||
// Set text material and font | |||||
text->set_material(&ctx->menu_font_material); | |||||
text->set_font(&ctx->menu_font); | |||||
// Set text color | |||||
if (i == ctx->options_menu_index) | |||||
text->set_color(active_color); | |||||
else | |||||
text->set_color(inactive_color); | |||||
// Update menu width | |||||
const auto& bounds = static_cast<const geom::aabb<float>&>(text->get_local_bounds()); | |||||
float width = bounds.max_point.x - bounds.min_point.x; | |||||
menu_width = std::max<float>(menu_width, width); | |||||
// Add text to UI | |||||
ctx->ui_scene->add_object(text); | |||||
} | |||||
// Align texts | |||||
float menu_height = ctx->options_menu_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->options_menu_texts.size(); ++i) | |||||
{ | |||||
scene::text* text = ctx->options_menu_texts[i]; | |||||
float x = menu_x; | |||||
float y = menu_y - ctx->menu_font.get_font_metrics().linespace * i; | |||||
text->set_translation({std::round(x), std::round(y), 0.0f}); | |||||
text->update_tweens(); | |||||
} | |||||
ctx->controls["menu_down"]->set_activated_callback | |||||
( | |||||
[ctx]() | |||||
{ | |||||
++ctx->options_menu_index; | |||||
if (ctx->options_menu_index >= ctx->options_menu_texts.size()) | |||||
ctx->options_menu_index = 0; | |||||
float4 active_color{1.0f, 1.0f, 1.0f, 1.0f}; | |||||
float4 inactive_color{1.0f, 1.0f, 1.0f, 0.5f}; | |||||
for (std::size_t i = 0; i < ctx->options_menu_texts.size(); ++i) | |||||
{ | |||||
scene::text* text = ctx->options_menu_texts[i]; | |||||
if (i == ctx->options_menu_index) | |||||
text->set_color(active_color); | |||||
else | |||||
text->set_color(inactive_color); | |||||
} | |||||
} | |||||
); | |||||
ctx->controls["menu_up"]->set_activated_callback | |||||
( | |||||
[ctx]() | |||||
{ | |||||
--ctx->options_menu_index; | |||||
if (ctx->options_menu_index < 0) | |||||
ctx->options_menu_index = ctx->options_menu_texts.size() - 1; | |||||
float4 active_color{1.0f, 1.0f, 1.0f, 1.0f}; | |||||
float4 inactive_color{1.0f, 1.0f, 1.0f, 0.5f}; | |||||
for (std::size_t i = 0; i < ctx->options_menu_texts.size(); ++i) | |||||
{ | |||||
scene::text* text = ctx->options_menu_texts[i]; | |||||
if (i == ctx->options_menu_index) | |||||
text->set_color(active_color); | |||||
else | |||||
text->set_color(inactive_color); | |||||
} | |||||
} | |||||
); | |||||
ctx->controls["menu_select"]->set_activated_callback | |||||
( | |||||
[ctx]() | |||||
{ | |||||
auto callback = ctx->options_menu_callbacks[ctx->options_menu_index]; | |||||
if (callback != nullptr) | |||||
callback(); | |||||
} | |||||
); | |||||
ctx->controls["menu_back"]->set_activated_callback(menu_back_callback); | |||||
/* | |||||
ctx->controls["menu_back"]->set_activated_callback | |||||
( | |||||
std::bind(&application::close, ctx->app, 0) | |||||
); | |||||
*/ | |||||
} | |||||
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_select"]->set_activated_callback(nullptr); | |||||
ctx->controls["menu_back"]->set_activated_callback(nullptr); | |||||
// Clear options menu callbacks | |||||
ctx->options_menu_callbacks.clear(); | |||||
// Destruct options menu texts | |||||
for (scene::text* text: ctx->options_menu_texts) | |||||
{ | |||||
ctx->ui_scene->remove_object(text); | |||||
delete text; | |||||
} | |||||
ctx->options_menu_texts.clear(); | |||||
ctx->ui_clear_pass->set_cleared_buffers(false, true, false); | |||||
} | |||||
} // namespace options_menu | |||||
} // namespace state | |||||
} // namespace game |
@ -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 <http://www.gnu.org/licenses/>. | |||||
*/ | |||||
#ifndef ANTKEEPER_GAME_STATE_OPTIONS_MENU_HPP | |||||
#define ANTKEEPER_GAME_STATE_OPTIONS_MENU_HPP | |||||
#include "game/context.hpp" | |||||
namespace game { | |||||
namespace state { | |||||
/// Options menu screen game state functions. | |||||
namespace options_menu { | |||||
void enter(game::context* ctx); | |||||
void exit(game::context* ctx); | |||||
} // namespace options_menu | |||||
} // namespace state | |||||
} // namespace game | |||||
#endif // ANTKEEPER_GAME_STATE_OPTIONS_MENU_HPP |