#include "appstates/TitleInfoState.hpp" #include "StateManager.hpp" #include "error.hpp" #include "graphics/colors.hpp" #include "input.hpp" #include "sdl.hpp" #include "strings/strings.hpp" #include "stringutil.hpp" #include namespace { /// @brief This is the width of the panel in pixels. constexpr int SIZE_PANEL_WIDTH = 480; /// @brief This is the gap on both sides constexpr int SIZE_PANEL_SUB = 16; /// @brief This is the size used for rendering text here. constexpr int SIZE_FONT = 24; /// @brief This is the vertical size in pixels of the render targets for title/publisher. constexpr int SIZE_TEXT_TARGET_HEIGHT = 40; } // namespace TitleInfoState::TitleInfoState(data::User *user, data::TitleInfo *titleInfo) : m_user(user) , m_titleInfo(titleInfo) , m_icon(m_titleInfo->get_icon()) { TitleInfoState::initialize_static_members(); TitleInfoState::create_info_scrolls(); } void TitleInfoState::update() { // Grab this instead of calling the function over and over. const bool hasFocus = BaseState::has_focus(); // Update slide panel. sm_slidePanel->update(hasFocus); if (input::button_pressed(HidNpadButton_B)) { sm_slidePanel->close(); } else if (sm_slidePanel->is_closed()) { TitleInfoState::deactivate_state(); } } void TitleInfoState::render() { const bool hasFocus = BaseState::has_focus(); sm_slidePanel->clear_target(); sdl::SharedTexture &panelTarget = sm_slidePanel->get_target(); m_icon->render_stretched(panelTarget, 88, 8, 304, 304); sm_slidePanel->render(sdl::Texture::Null, hasFocus); } void TitleInfoState::initialize_static_members() { if (!sm_slidePanel) { sm_slidePanel = std::make_unique(SIZE_PANEL_WIDTH, ui::SlideOutPanel::Side::Right); } } void TitleInfoState::create_info_scrolls() { static constexpr int SIZE_VERT_GAP = SIZE_TEXT_TARGET_HEIGHT + 4; static constexpr int COORD_INIT_Y = 278; const uint64_t applicationID = m_titleInfo->get_application_id(); const FsSaveDataInfo *saveInfo = m_user->get_save_info_by_id(applicationID); const PdmPlayStatistics *playStats = m_user->get_play_stats_by_id(applicationID); if (error::is_null(saveInfo) || error::is_null(playStats)) { return; } const char *appIDFormat = strings::get_by_name(strings::names::TITLEINFO, 0); const char *saveIDFormat = strings::get_by_name(strings::names::TITLEINFO, 1); const char *firstPlayedFormat = strings::get_by_name(strings::names::TITLEINFO, 2); const char *lastPlayedFormat = strings::get_by_name(strings::names::TITLEINFO, 3); const char *playTimeFormat = strings::get_by_name(strings::names::TITLEINFO, 4); const char *launchesFormat = strings::get_by_name(strings::names::TITLEINFO, 5); const char *saveTypeFormat = strings::get_by_name(strings::names::TITLEINFO, 6); // Gonna push this all here and loop it later to make this "easier." std::vector textVector{}; textVector.push_back(m_titleInfo->get_title()); textVector.push_back(m_titleInfo->get_publisher()); const std::string appIDString = stringutil::get_formatted_string(appIDFormat, applicationID); textVector.push_back(appIDString); const std::string saveIDString = stringutil::get_formatted_string(saveIDFormat, saveInfo->save_data_id); textVector.push_back(saveIDString); std::array firstPlayed = {0}; const std::time_t firstTime = static_cast(playStats->first_timestamp_user); const std::tm *firstLocal = std::localtime(&firstTime); std::strftime(firstPlayed.data(), 0x40, firstPlayedFormat, firstLocal); textVector.push_back(firstPlayed.data()); std::array lastPlayed = {0}; const std::time_t lastTime = static_cast(playStats->last_timestamp_user); const std::tm *lastLocal = std::localtime(&lastTime); std::strftime(lastPlayed.data(), 0x40, lastPlayedFormat, lastLocal); textVector.push_back(lastPlayed.data()); int64_t seconds = playStats->playtime / 1000000000; int64_t hours = seconds / 3600; int64_t minutes = (seconds % 3600) / 60; seconds %= 60; const std::string playTimeString = stringutil::get_formatted_string(playTimeFormat, hours, minutes, seconds); textVector.push_back(playTimeString); const std::string launchesString = stringutil::get_formatted_string(launchesFormat, playStats->total_launches); textVector.push_back(launchesString); const char *saveType = strings::get_by_name(strings::names::SAVE_DATA_TYPES, saveInfo->save_data_type); const std::string saveTypeString = stringutil::get_formatted_string(saveTypeFormat, saveType); textVector.push_back(saveTypeString); // Just loop, I guess. int y = COORD_INIT_Y; for (const std::string_view &string : textVector) { auto newField = TitleInfoState::create_new_scroll(string, (y += SIZE_VERT_GAP)); sm_slidePanel->push_new_element(newField); } } std::shared_ptr TitleInfoState::create_new_scroll(std::string_view text, int y) { static constexpr int SIZE_FIELD_WIDTH = SIZE_PANEL_WIDTH - SIZE_PANEL_SUB; auto textFieldScroll = ui::TextScroll::create(text, 8, y, SIZE_FIELD_WIDTH, SIZE_TEXT_TARGET_HEIGHT, SIZE_FONT, colors::WHITE, m_fieldClear ? colors::CLEAR_COLOR : colors::DIALOG_DARK, false); m_fieldClear = m_fieldClear ? false : true; return textFieldScroll; } void TitleInfoState::deactivate_state() { sm_slidePanel->clear_elements(); sm_slidePanel->reset(); BaseState::deactivate(); }