diff --git a/include/appstates/BackupMenuState.hpp b/include/appstates/BackupMenuState.hpp index bc9dd33..4292815 100644 --- a/include/appstates/BackupMenuState.hpp +++ b/include/appstates/BackupMenuState.hpp @@ -6,6 +6,7 @@ #include "system/Timer.hpp" #include "ui/Menu.hpp" #include "ui/SlideOutPanel.hpp" +#include "ui/TextScroll.hpp" #include /// @brief This is the state where the user can backup and restore saves. @@ -49,6 +50,9 @@ class BackupMenuState : public AppState /// @brief Directory listing of the above. fslib::Directory m_directoryListing; + /// @brief This is the scrolling text at the top. + ui::TextScroll m_titleScroll; + /// @brief Variable that saves whether or not the filesystem has data in it. bool m_saveHasData = false; diff --git a/include/appstates/TitleInfoState.hpp b/include/appstates/TitleInfoState.hpp index 438869c..a150ef3 100644 --- a/include/appstates/TitleInfoState.hpp +++ b/include/appstates/TitleInfoState.hpp @@ -34,12 +34,36 @@ class TitleInfoState : public AppState /// @brief This is a pointer to the title's icon. sdl::SharedTexture m_icon; + /// @brief This is the scrolling text for the title. + ui::TextScroll m_titleScroll; + + /// @brief This is the scrolling text for the publisher. + ui::TextScroll m_publisherScroll; + + /// @brief This string holds the application ID. + std::string m_applicationID; + + /// @brief This holds the hex save data id of the file on nand. + std::string m_saveDataID; + + /// @brief This holds the play time string. + std::string m_playTime; + + /// @brief This holds the total launches string. + std::string m_totalLaunches; + + /// @brief This holds the save data type string. + std::string m_saveDataType; + /// @brief Bool to tell whether or not static members are initialized. static inline bool sm_initialized = false; /// @brief This is the render target for the title text just in case it needs scrolling. static inline sdl::SharedTexture sm_titleTarget = nullptr; + /// @brief This is the render target for the publisher string. + static inline sdl::SharedTexture sm_publisherTarget = nullptr; + /// @brief Slide panel. static inline std::unique_ptr sm_slidePanel = nullptr; }; diff --git a/include/strings.hpp b/include/strings.hpp index 9d147a9..459f521 100644 --- a/include/strings.hpp +++ b/include/strings.hpp @@ -33,6 +33,7 @@ namespace strings static constexpr std::string_view TITLE_OPTION_STATUS = "TitleOptionStatus"; static constexpr std::string_view TITLE_OPTION_POPS = "TitleOptionPops"; static constexpr std::string_view TITLE_OPTION_CONFIRMATIONS = "TitleOptionConfirmations"; + static constexpr std::string_view TITLE_INFO_STRINGS = "TitleInfo"; static constexpr std::string_view POP_MESSAGES_GENERAL = "PopMessagesGeneral"; static constexpr std::string_view POP_MESSAGES_BACKUP_MENU = "PopMessagesBackupMenu"; static constexpr std::string_view POP_MESSAGES_SAVE_CREATE = "PopMessagesSaveCreate"; diff --git a/include/system/Timer.hpp b/include/system/Timer.hpp index 1f41bf4..752e6b5 100644 --- a/include/system/Timer.hpp +++ b/include/system/Timer.hpp @@ -15,11 +15,6 @@ namespace sys /// @param triggerTicks Number of ticks the timer is triggered at. Timer(uint64_t triggerTicks); - /// @brief Copy operator. - /// @param timer Timer to copy. - /// @return Reference to copied timer. - Timer &operator=(const Timer &timer); - /// @brief Starts the timer. /// @param triggerTicks Number of ticks to trigger at. void start(uint64_t triggerTicks); diff --git a/include/ui/TextScroll.hpp b/include/ui/TextScroll.hpp index 2a06d86..b6a74f9 100644 --- a/include/ui/TextScroll.hpp +++ b/include/ui/TextScroll.hpp @@ -12,18 +12,26 @@ namespace ui /// @brief This is only here so I can get around the backup menu having static members. TextScroll(void) = default; - /// @brief Constructs a new TextScroll. - /// @param text Text to be scrolled. - /// @param availableWidth Maximum width. If this is not exceeded,text is just rendered centered according to it. - TextScroll(std::string_view text, int fontSize, int availableWidth, int y, sdl::Color color); + /// @brief Constructor for TextScroll. + /// @param text Text to create the textscroll with. + /// @param fontSize Size of the font in pixels to use. + /// @param availableWidth Available width of the render target. + /// @param y Y coordinate to render to. + /// @param center Whether or not text should be centered if it's not wide enough for scrolling. + /// @param color Color to use to render the text. + TextScroll(std::string_view text, int fontSize, int availableWidth, int y, bool center, sdl::Color color); /// @brief Required destructor. ~TextScroll() {}; - /// @brief Same as the default constructor. To do: Figure this out better sometime. - /// @param textScroll TextScroll to copy. - /// @return Reference to itself? - TextScroll &operator=(const TextScroll &textScroll); + /// @brief Creates/sets the text and parameters for TextScroll. + /// @param text Text to display/scroll. + /// @param fontSize Font size to use when rendering. + /// @param availableWidth Available width of the target buffer. + /// @param y Y coordinate used to render text. + /// @param center Whether or not text should be centered if it's not wide enough for scrolling. + /// @param color Color to use to render text. + void create(std::string_view text, int fontSize, int availableWidth, int y, bool center, sdl::Color color); /// @brief Runs the update routine. /// @param hasFocus Whether or not the calling state has focus. diff --git a/romfs/Text/ENUS.json b/romfs/Text/ENUS.json index e41f7a8..8ad9ddf 100644 --- a/romfs/Text/ENUS.json +++ b/romfs/Text/ENUS.json @@ -159,6 +159,13 @@ "Are you sure you would like to reset the save data for #%s#? *This will delete the current save data for the title as if it were never run!*", "Are you sure you want to delete `%s`'s save data for #%s#? This will permanently delete it from the system." ], + "TitleInfo": [ + "App ID: %016lX", + "Save ID: %016lx", + "Play Time: %02d:%02d:%02d", + "Launches: %i", + "Save Type: %s" + ], "PopMessagesGeneral": [ "Unable to exit JKSV while tasks are running!" ], diff --git a/source/appstates/BackupMenuState.cpp b/source/appstates/BackupMenuState.cpp index 148c7c1..ac7b2e1 100644 --- a/source/appstates/BackupMenuState.cpp +++ b/source/appstates/BackupMenuState.cpp @@ -75,7 +75,7 @@ BackupMenuState::BackupMenuState(data::User *user, data::TitleInfo *titleInfo, F stringutil::get_formatted_string("`%s` - %s", m_user->get_nickname(), m_titleInfo->get_title()); // This needs sm_panelWidth or it'd be in the initializer list. - sm_slidePanel->push_new_element(std::make_shared(panelString, 22, sm_panelWidth, 8, colors::WHITE)); + m_titleScroll.create(panelString, 22, sm_panelWidth, 8, true, colors::WHITE); fslib::Directory saveCheck(fs::DEFAULT_SAVE_ROOT); m_saveHasData = saveCheck.get_count() > 0; @@ -90,6 +90,8 @@ BackupMenuState::~BackupMenuState() void BackupMenuState::update(void) { + bool hasFocus = AppState::has_focus(); + if (input::button_pressed(HidNpadButton_A) && sm_backupMenu->get_selected() == 0 && m_saveHasData) { // get name for backup. @@ -230,20 +232,29 @@ void BackupMenuState::update(void) AppState::deactivate(); } + // Update title scrolling. + m_titleScroll.update(hasFocus); + // Update panel. - sm_slidePanel->update(AppState::has_focus()); + sm_slidePanel->update(hasFocus); // This state bypasses the Slideout panel's normal behavior because it kind of has to. - sm_backupMenu->update(AppState::has_focus()); + sm_backupMenu->update(hasFocus); } void BackupMenuState::render(void) { + // Save this locally. + bool hasFocus = AppState::has_focus(); + // Clear panel target. sm_slidePanel->clear_target(); // Grab the render target. SDL_Texture *slideTarget = sm_slidePanel->get_target(); + // Start with [Name] - [Title] + m_titleScroll.render(slideTarget, hasFocus); + sdl::render_line(slideTarget, 10, 42, sm_panelWidth - 10, 42, colors::WHITE); sdl::render_line(slideTarget, 10, 648, sm_panelWidth - 10, 648, colors::WHITE); sdl::text::render(slideTarget, @@ -257,11 +268,11 @@ void BackupMenuState::render(void) // Clear menu target. sm_menuRenderTarget->clear(colors::TRANSPARENT); // render menu to it. - sm_backupMenu->render(sm_menuRenderTarget->get(), AppState::has_focus()); + sm_backupMenu->render(sm_menuRenderTarget->get(), hasFocus); // render it to panel target. sm_menuRenderTarget->render(slideTarget, 0, 43); - sm_slidePanel->render(NULL, AppState::has_focus()); + sm_slidePanel->render(NULL, hasFocus); } void BackupMenuState::refresh(void) diff --git a/source/appstates/TitleInfoState.cpp b/source/appstates/TitleInfoState.cpp index 91ad01a..dbb772a 100644 --- a/source/appstates/TitleInfoState.cpp +++ b/source/appstates/TitleInfoState.cpp @@ -2,11 +2,22 @@ #include "colors.hpp" #include "input.hpp" #include "sdl.hpp" +#include "strings.hpp" +#include "stringutil.hpp" 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) @@ -17,12 +28,59 @@ TitleInfoState::TitleInfoState(data::User *user, data::TitleInfo *titleInfo) { // This is the slide panel everything is rendered to. sm_slidePanel = std::make_unique(SIZE_PANEL_WIDTH, ui::SlideOutPanel::Side::Right); + + // This is the render target for the title. sm_titleTarget = sdl::TextureManager::create_load_texture("infoTitleTarget", - SIZE_PANEL_WIDTH - 32, - 32, + SIZE_PANEL_WIDTH - SIZE_PANEL_SUB, + SIZE_TEXT_TARGET_HEIGHT, SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET); + + // This is the render target for the publisher. + sm_publisherTarget = + sdl::TextureManager::create_load_texture("infoPublisherTarget", + SIZE_PANEL_WIDTH - SIZE_PANEL_SUB, + SIZE_TEXT_TARGET_HEIGHT, + SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET); sm_initialized = true; } + + // Do this instead of calling the function everytime. + uint64_t applicationID = m_titleInfo->get_application_id(); + + // These is needed at some point. + FsSaveDataInfo *saveInfo = m_user->get_save_info_by_id(applicationID); + PdmPlayStatistics *playStats = m_user->get_play_stats_by_id(applicationID); + + // Title text. + m_titleScroll + .create(m_titleInfo->get_title(), SIZE_FONT, SIZE_PANEL_WIDTH - SIZE_PANEL_SUB, 6, false, colors::WHITE); + // Publisher. + m_publisherScroll + .create(m_titleInfo->get_publisher(), SIZE_FONT, SIZE_PANEL_WIDTH - SIZE_PANEL_SUB, 6, false, colors::WHITE); + + // Grab the application ID string. + m_applicationID = + stringutil::get_formatted_string(strings::get_by_name(strings::names::TITLE_INFO_STRINGS, 0), applicationID); + // Same here. I think these use lower case characters on the NAND? + m_saveDataID = stringutil::get_formatted_string(strings::get_by_name(strings::names::TITLE_INFO_STRINGS, 1), + saveInfo->save_data_id); + // This is simple. + m_totalLaunches = stringutil::get_formatted_string(strings::get_by_name(strings::names::TITLE_INFO_STRINGS, 3), + playStats->total_launches); + // This should be semi-simple. + m_saveDataType = stringutil::get_formatted_string( + strings::get_by_name(strings::names::TITLE_INFO_STRINGS, 4), + strings::get_by_name(strings::names::SAVE_DATA_TYPES, saveInfo->save_data_type)); + + // Calculate play time. We're going to use non-floating point to truncate the remainders. + int64_t seconds = playStats->playtime / static_cast(1e+9); + int64_t hours = seconds / 3600; + int64_t minutes = (seconds % 3600) / 60; + seconds %= 60; + m_playTime = stringutil::get_formatted_string(strings::get_by_name(strings::names::TITLE_INFO_STRINGS, 2), + hours, + minutes, + seconds); } TitleInfoState::~TitleInfoState() @@ -33,8 +91,15 @@ TitleInfoState::~TitleInfoState() void TitleInfoState::update(void) { + // Grab this instead of calling the function over and over. + bool hasFocus = AppState::has_focus(); + // Update slide panel. - sm_slidePanel->update(AppState::has_focus()); + sm_slidePanel->update(hasFocus); + + // Update the scrolling text. + m_titleScroll.update(hasFocus); + m_publisherScroll.update(hasFocus); if (input::button_pressed(HidNpadButton_B)) { @@ -49,18 +114,110 @@ void TitleInfoState::update(void) void TitleInfoState::render(void) { + // This is the vertical gap for rendering text. + static constexpr int SIZE_VERT_GAP = SIZE_TEXT_TARGET_HEIGHT + 4; + // This is the size to render the rectangles at. + static constexpr int SIZE_RECT_WIDTH = SIZE_PANEL_WIDTH - SIZE_PANEL_SUB; + + // This is whether or not the state currently has focus. + bool hasFocus = AppState::has_focus(); + + // Clear the title and publisher targets. + sm_titleTarget->clear(colors::DIALOG_BOX); + sm_publisherTarget->clear(colors::CLEAR_COLOR); + // Grab the panel's target and clear it. To do: This how I originally intended to. sm_slidePanel->clear_target(); // Grab the panel target for rendering to. SDL_Texture *panelTarget = sm_slidePanel->get_target(); + // This is our Y rendering coordinate. It's easier to adjust this as needed than change everything. + // This starts under the icon. + int y = 304; + // Start by rendering the icon. - m_icon->render(panelTarget, 112, 24); + m_icon->render_stretched(panelTarget, 80, 8, 320, 320); - // Clear the title target and render the text scroll to it. - sm_titleTarget->clear(colors::DIALOG_BOX); + // Render the textscroll to the target, then target to the panel target. + m_titleScroll.render(sm_titleTarget->get(), hasFocus); + sm_titleTarget->render(panelTarget, 8, (y += SIZE_VERT_GAP)); + // Publisher. + m_publisherScroll.render(sm_publisherTarget->get(), hasFocus); + sm_publisherTarget->render(panelTarget, 8, (y += SIZE_VERT_GAP)); + + // These are different since the probably won't go outside the bounds of the rectangle. + sdl::render_rect_fill(panelTarget, + 8, + (y += SIZE_VERT_GAP), + SIZE_RECT_WIDTH, + SIZE_TEXT_TARGET_HEIGHT, + colors::DIALOG_BOX); + // Text needs to be aligned like the scrolling text. + sdl::text::render(panelTarget, + 16, + y + 6, + SIZE_FONT, + sdl::text::NO_TEXT_WRAP, + colors::WHITE, + m_applicationID.c_str()); + + + // These are different since the probably won't go outside the bounds of the rectangle. + sdl::render_rect_fill(panelTarget, + 8, + (y += SIZE_VERT_GAP), + SIZE_RECT_WIDTH, + SIZE_TEXT_TARGET_HEIGHT, + colors::CLEAR_COLOR); + // Text needs to be aligned like the scrolling text. + sdl::text::render(panelTarget, 16, y + 6, SIZE_FONT, sdl::text::NO_TEXT_WRAP, colors::WHITE, m_saveDataID.c_str()); + + + // These are different since the probably won't go outside the bounds of the rectangle. + sdl::render_rect_fill(panelTarget, + 8, + (y += SIZE_VERT_GAP), + SIZE_RECT_WIDTH, + SIZE_TEXT_TARGET_HEIGHT, + colors::DIALOG_BOX); + // Text needs to be aligned like the scrolling text. + sdl::text::render(panelTarget, 16, y + 6, SIZE_FONT, sdl::text::NO_TEXT_WRAP, colors::WHITE, m_playTime.c_str()); + + + // These are different since the probably won't go outside the bounds of the rectangle. + sdl::render_rect_fill(panelTarget, + 8, + (y += SIZE_VERT_GAP), + SIZE_RECT_WIDTH, + SIZE_TEXT_TARGET_HEIGHT, + colors::CLEAR_COLOR); + // Text needs to be aligned like the scrolling text. + sdl::text::render(panelTarget, + 16, + y + 6, + SIZE_FONT, + sdl::text::NO_TEXT_WRAP, + colors::WHITE, + m_totalLaunches.c_str()); + + + // These are different since the probably won't go outside the bounds of the rectangle. + sdl::render_rect_fill(panelTarget, + 8, + (y += SIZE_VERT_GAP), + SIZE_RECT_WIDTH, + SIZE_TEXT_TARGET_HEIGHT, + colors::DIALOG_BOX); + // Text needs to be aligned like the scrolling text. + sdl::text::render(panelTarget, + 16, + y + 6, + SIZE_FONT, + sdl::text::NO_TEXT_WRAP, + colors::WHITE, + m_saveDataType.c_str()); sm_slidePanel->render(NULL, AppState::has_focus()); } diff --git a/source/data/TitleInfo.cpp b/source/data/TitleInfo.cpp index 247e900..dc80d17 100644 --- a/source/data/TitleInfo.cpp +++ b/source/data/TitleInfo.cpp @@ -21,9 +21,6 @@ data::TitleInfo::TitleInfo(uint64_t applicationID) : m_applicationID(application if (R_FAILED(nsError) || nsAppControlSize < sizeof(m_data.nacp)) { - // This should be false by default, but just in case. - m_hasData = false; - // This is the lowest four hex values of the title. std::string applicationIDHex = stringutil::get_formatted_string("%04X", m_applicationID & 0xFFFF); diff --git a/source/data/data.cpp b/source/data/data.cpp index 3a1a673..927c6a2 100644 --- a/source/data/data.cpp +++ b/source/data/data.cpp @@ -342,7 +342,7 @@ static void load_save_data_info(void) default: { - // Default is just the ID in the save info struct. + // Default is just the ID in the save info struct. This should be fine for system saves too. accountID = saveInfo.uid; } break; @@ -383,9 +383,10 @@ static void load_save_data_info(void) // I feel weird allcating space for this even if it's not used, but whatever. PdmPlayStatistics stats = {0}; // This should be an OKish way to filter out system titles... - if (titleInfo.has_control_data() && - R_FAILED( - pdmqryQueryPlayStatisticsByApplicationIdAndUserAccountId(applicationID, accountID, false, &stats))) + if (R_FAILED(pdmqryQueryPlayStatisticsByApplicationIdAndUserAccountId(applicationID, + saveInfo.uid, + false, + &stats))) { // This isn't fatal. logger::log("Error getting play stats for title %016llX!", applicationID); diff --git a/source/system/Timer.cpp b/source/system/Timer.cpp index 09b2a8d..ee78ed0 100644 --- a/source/system/Timer.cpp +++ b/source/system/Timer.cpp @@ -1,21 +1,20 @@ #include "system/Timer.hpp" +#include + +#include "logger.hpp" sys::Timer::Timer(uint64_t triggerTicks) { Timer::start(triggerTicks); } -sys::Timer &sys::Timer::operator=(const sys::Timer &timer) -{ - m_startingTicks = timer.m_startingTicks; - m_triggerTicks = timer.m_triggerTicks; - return *this; -} - void sys::Timer::start(uint64_t triggerTicks) { - m_startingTicks = SDL_GetTicks64(); + // Start by recording trigger ticks. m_triggerTicks = triggerTicks; + + // Get the current tick count. + m_startingTicks = SDL_GetTicks64(); } bool sys::Timer::is_triggered(void) @@ -27,8 +26,10 @@ bool sys::Timer::is_triggered(void) { return false; } + // Reset starting ticks. m_startingTicks = currentTicks; + // Trigger me timbers~ return true; } diff --git a/source/ui/TextScroll.cpp b/source/ui/TextScroll.cpp index 5433c62..319b66a 100644 --- a/source/ui/TextScroll.cpp +++ b/source/ui/TextScroll.cpp @@ -1,37 +1,59 @@ #include "ui/TextScroll.hpp" #include "sdl.hpp" -ui::TextScroll::TextScroll(std::string_view text, int fontSize, int availableWidth, int y, sdl::Color color) - : m_text(text.data()), m_y(y), m_fontSize(fontSize), m_textColor(color), m_scrollTimer(3000) -{ - // Grab text width first. - m_textWidth = sdl::text::get_width(m_fontSize, m_text.c_str()); +#include "logger.hpp" - // Check if text needs scrolling for width provided. +namespace +{ + /// @brief This is the number of ticks needed before the text starts scrolling. + constexpr uint64_t TICKS_SCROLL_TRIGGER = 3000; +} // namespace + +ui::TextScroll::TextScroll(std::string_view text, + int fontSize, + int availableWidth, + int y, + bool center, + sdl::Color color) +{ + TextScroll::create(text, fontSize, availableWidth, y, center, color); +} + +void ui::TextScroll::create(std::string_view text, + int fontSize, + int availableWidth, + int y, + bool center, + sdl::Color color) +{ + // Copy the text and stuff. + m_text = text; + m_y = y; + m_fontSize = fontSize; + m_textColor = color; + m_scrollTimer.start(TICKS_SCROLL_TRIGGER); + + // Get the width and calculate X. + m_textWidth = sdl::text::get_width(m_fontSize, m_text.c_str()); if (m_textWidth > availableWidth) { + // Set the X coordinate to 8 and make sure this knows it needs to scroll. m_x = 8; m_textScrolling = true; + logger::log("Scrolling needed?"); } - else + else if (center) { // Just center it. m_x = (availableWidth / 2) - (m_textWidth / 2); + logger::log("Centered."); + } + else + { + // Just set this to 8. To do: Figure out how to make this cleaner. + m_x = 8; + logger::log("Aligned."); } -} - -ui::TextScroll &ui::TextScroll::operator=(const ui::TextScroll &textScroll) -{ - m_text = textScroll.m_text; - m_x = textScroll.m_x; - m_y = textScroll.m_y; - m_fontSize = textScroll.m_fontSize; - m_textColor = textScroll.m_textColor; - m_textWidth = textScroll.m_textWidth; - m_textScrolling = textScroll.m_textScrolling; - m_textScrollTriggered = textScroll.m_textScrollTriggered; - m_scrollTimer = textScroll.m_scrollTimer; - return *this; } void ui::TextScroll::update(bool hasFocus) @@ -39,6 +61,7 @@ void ui::TextScroll::update(bool hasFocus) // I don't think needs to care about having focus. if (m_textScrolling && m_scrollTimer.is_triggered()) { + logger::log("Text scroll triggered?"); m_x -= 2; m_textScrollTriggered = true; }