#include "menu.h" #include #include #include #include "../base/collusion_test.h" #include "../base/interpolation.h" #include "../base/log.h" #include "../base/worker.h" #include "../engine/engine.h" #include "../engine/font.h" #include "../engine/image.h" #include "../engine/input_event.h" #include "../engine/renderer/texture.h" #include "demo.h" using namespace base; using namespace eng; namespace { constexpr char kMenuOption[Menu::kOption_Max][10] = {"continue", "start", "credits", "exit"}; constexpr float kMenuOptionSpace = 1.5f; const Vector4 kColorNormal = {1, 1, 1, 1}; const Vector4 kColorHighlight = {5, 5, 5, 1}; constexpr float kBlendingSpeed = 0.12f; const Vector4 kColorFadeOut = {1, 1, 1, 0}; constexpr float kFadeSpeed = 0.2f; } // namespace Menu::Menu() : tex_(Engine::Get().CreateRenderResource()) {} Menu::~Menu() = default; bool Menu::Initialize() { const Font& font = static_cast(Engine::Get().GetGame())->GetFont(); max_text_width_ = -1; for (int i = 0; i < kOption_Max; ++i) { int width, height; font.CalculateBoundingBox(kMenuOption[i], width, height); if (width > max_text_width_) max_text_width_ = width; } tex_->Update(CreateImage()); for (int i = 0; i < kOption_Max; ++i) { items_[i].text.Create(tex_, {1, 4}); items_[i].text.AutoScale(); items_[i].text.Scale(1.5f); items_[i].text.SetColor(kColorFadeOut); items_[i].text.SetVisible(false); items_[i].text.SetFrame(i); items_[i].select_item_cb_ = [&, i]() -> void { items_[i].text_animator.SetEndCallback( Animator::kBlending, [&, i]() -> void { items_[i].text_animator.SetEndCallback(Animator::kBlending, nullptr); selected_option_ = (Option)i; }); items_[i].text_animator.SetBlending(kColorNormal, kBlendingSpeed); items_[i].text_animator.Play(Animator::kBlending, false); }; items_[i].text_animator.Attach(&items_[i].text); } // Get the item positions calculated. SetOptionEnabled(kContinue, true); return true; } void Menu::Update(float delta_time) { for (int i = 0; i < kOption_Max; ++i) { if (items_[i].hide) continue; items_[i].text_animator.Update(delta_time); } } void Menu::OnInputEvent(std::unique_ptr event) { if (event->GetType() == InputEvent::kTap || event->GetType() == InputEvent::kDragStart) tap_pos_[0] = tap_pos_[1] = event->GetVector(0); else if (event->GetType() == InputEvent::kDrag) tap_pos_[1] = event->GetVector(0); if ((event->GetType() != InputEvent::kTap && event->GetType() != InputEvent::kDragEnd) || IsAnimating()) return; for (int i = 0; i < kOption_Max; ++i) { if (items_[i].hide) continue; if (!Intersection(items_[i].text.GetOffset(), items_[i].text.GetScale() * Vector2(1.2f, 2), tap_pos_[0])) continue; if (!Intersection(items_[i].text.GetOffset(), items_[i].text.GetScale() * Vector2(1.2f, 2), tap_pos_[1])) continue; items_[i].text_animator.SetEndCallback(Animator::kBlending, items_[i].select_item_cb_); items_[i].text_animator.SetBlending(kColorHighlight, kBlendingSpeed); items_[i].text_animator.Play(Animator::kBlending, false); } } void Menu::Draw() { for (int i = 0; i < kOption_Max; ++i) items_[i].text.Draw(); } void Menu::ContextLost() { tex_->Update(CreateImage()); } void Menu::SetOptionEnabled(Option o, bool enable) { int first = -1, last = -1; for (int i = 0; i < kOption_Max; ++i) { if (i == o) items_[i].hide = !enable; if (!items_[i].hide) { items_[i].text.SetOffset({0, 0}); if (last >= 0) { items_[i].text.PlaceToBottomOf(items_[last].text); items_[i].text.Translate(items_[last].text.GetOffset() * Vector2(0, 1)); items_[i].text.Translate( {0, items_[last].text.GetScale().y * -kMenuOptionSpace}); } if (first < 0) first = i; last = i; } } float center_offset_y = (items_[first].text.GetOffset().y - items_[last].text.GetOffset().y) / 2; for (int i = 0; i < kOption_Max; ++i) { if (!items_[i].hide) items_[i].text.Translate({0, center_offset_y}); } } void Menu::Show() { for (int i = 0; i < kOption_Max; ++i) { if (items_[i].hide) continue; items_[i].text_animator.SetEndCallback( Animator::kBlending, [&, i]() -> void { items_[i].text_animator.SetEndCallback(Animator::kBlending, nullptr); }); items_[i].text_animator.SetBlending(kColorNormal, kFadeSpeed); items_[i].text_animator.Play(Animator::kBlending, false); items_[i].text.SetVisible(true); } } void Menu::Hide() { selected_option_ = kOption_Invalid; for (int i = 0; i < kOption_Max; ++i) { if (items_[i].hide) continue; items_[i].text_animator.SetEndCallback( Animator::kBlending, [&, i]() -> void { items_[i].text_animator.SetEndCallback(Animator::kBlending, nullptr); items_[i].text.SetVisible(false); }); items_[i].text_animator.SetBlending(kColorFadeOut, kFadeSpeed); items_[i].text_animator.Play(Animator::kBlending, false); } } std::unique_ptr Menu::CreateImage() { const Font& font = static_cast(Engine::Get().GetGame())->GetFont(); int line_height = font.GetLineHeight() + 1; auto image = std::make_unique(); image->Create(max_text_width_, line_height * kOption_Max); // Fill the area of each menu item with gradient. image->GradientV({1.0f, 1.0f, 1.0f, 0}, {.0f, .0f, 1.0f, 0}, line_height); base::Worker worker(kOption_Max); for (int i = 0; i < kOption_Max; ++i) { int w, h; font.CalculateBoundingBox(kMenuOption[i], w, h); float x = (image->GetWidth() - w) / 2; float y = line_height * i; worker.Enqueue(std::bind(&Font::Print, &font, x, y, kMenuOption[i], image->GetBuffer(), image->GetWidth())); } worker.Join(); return image; } bool Menu::IsAnimating() { for (int i = 0; i < kOption_Max; ++i) { if (items_[i].text_animator.IsPlaying(Animator::kBlending)) return true; } return false; }