From 5cab0d505b8773c6674a4e84f71cd052a69055b8 Mon Sep 17 00:00:00 2001 From: J-D-K Date: Fri, 13 Jun 2025 11:23:59 -0400 Subject: [PATCH] Reimplement Wii loading glyph for tasks. --- include/appstates/BaseTask.hpp | 42 +++++++++++++++++++++++++++++ include/appstates/ProgressState.hpp | 6 ++--- include/appstates/TaskState.hpp | 6 ++--- include/ui/ColorMod.hpp | 7 +++-- include/ui/render_functions.hpp | 3 ++- source/appstates/BaseTask.cpp | 37 +++++++++++++++++++++++++ source/appstates/ProgressState.cpp | 6 +++++ source/appstates/TaskState.cpp | 5 ++++ source/ui/ColorMod.cpp | 4 +-- source/ui/render_functions.cpp | 15 +++++------ 10 files changed, 111 insertions(+), 20 deletions(-) create mode 100644 include/appstates/BaseTask.hpp create mode 100644 source/appstates/BaseTask.cpp diff --git a/include/appstates/BaseTask.hpp b/include/appstates/BaseTask.hpp new file mode 100644 index 0000000..eb98ffe --- /dev/null +++ b/include/appstates/BaseTask.hpp @@ -0,0 +1,42 @@ +#pragma once +#include "appstates/AppState.hpp" +#include "system/Timer.hpp" +#include "ui/ColorMod.hpp" +#include +#include + +/// @brief Normally, I wouldn't do this, but this holds a single function both TaskState and ProgressState share... +class BaseTask : public AppState +{ + public: + /// @brief Constructor. Starts the glyph timer and sets AppState to not allow closing. + BaseTask(void); + + /// @brief Virtual destructor. + virtual ~BaseTask() {}; + + /// @brief Runs the update routine for rendering the loading glyph animation. + /// @param + void update(void) override; + + /// @brief Virtual render function. + virtual void render(void) = 0; + + /// @brief This function renders the loading glyph in the bottom left corner. + /// @note This is mostly just so users don't think JKSV has frozen when operations take a long time. + void render_loading_glyph(void); + + private: + /// @brief This is the current frame of the loading glyph animation. + int m_currentFrame = 0; + + /// @brief This is the timer used for changing the current glyph/frame of the animation. + sys::Timer m_frameTimer; + + /// @brief This is used to give the animation its pulsing color. + ui::ColorMod m_colorMod; + + /// @brief This array holds the glyphs of the loading sequence. I think it's from the Wii? + static inline std::array sm_glyphArray = + {"\ue020", "\ue021", "\ue022", "\ue023", "\ue024", "\ue025", "\ue026", "\ue027"}; +}; diff --git a/include/appstates/ProgressState.hpp b/include/appstates/ProgressState.hpp index f9ae5ea..01132c7 100644 --- a/include/appstates/ProgressState.hpp +++ b/include/appstates/ProgressState.hpp @@ -1,11 +1,11 @@ #pragma once -#include "appstates/AppState.hpp" +#include "appstates/BaseTask.hpp" #include "system/ProgressTask.hpp" #include #include /// @brief State that shows progress of a task. -class ProgressState : public AppState +class ProgressState : public BaseTask { public: /// @brief Constructs a new ProgressState. @@ -14,7 +14,7 @@ class ProgressState : public AppState /// @note All functions passed to this must follow this signature: void function(sys::ProgressTask *, ) template ProgressState(void (*function)(sys::ProgressTask *, Args...), Args... args) - : AppState(false), m_task(function, std::forward(args)...){}; + : BaseTask(), m_task(function, std::forward(args)...){}; /// @brief Required destructor. ~ProgressState() {}; diff --git a/include/appstates/TaskState.hpp b/include/appstates/TaskState.hpp index 3a6595a..c5a8640 100644 --- a/include/appstates/TaskState.hpp +++ b/include/appstates/TaskState.hpp @@ -1,10 +1,10 @@ #pragma once -#include "appstates/AppState.hpp" +#include "appstates/BaseTask.hpp" #include "system/Task.hpp" #include /// @brief State that spawns a task and allows updates to be printed to screen. -class TaskState : public AppState +class TaskState : public BaseTask { public: /// @brief Constructs and spawns a new TaskState. @@ -13,7 +13,7 @@ class TaskState : public AppState /// @note All functions passed must follow this signature: void function(sys::Task *, ) template TaskState(void (*function)(sys::Task *, Args...), Args... args) - : AppState(false), m_task(function, std::forward(args)...){}; + : BaseTask(), m_task(function, std::forward(args)...){}; /// @brief Required destructor. ~TaskState() {}; diff --git a/include/ui/ColorMod.hpp b/include/ui/ColorMod.hpp index 1770efd..d01f5bb 100644 --- a/include/ui/ColorMod.hpp +++ b/include/ui/ColorMod.hpp @@ -1,4 +1,5 @@ #pragma once +#include "sdl.hpp" #include namespace ui @@ -13,12 +14,14 @@ namespace ui /// @brief Updates the color modification variable. void update(void); - /// @brief Allows me to use this like it's a uint8_t directly. - operator uint8_t(void) const; + /// @brief Operator that allows using this as an sdl::Color directly. + /// @note Since all of these pulse the same color, no sense in not doing this. + operator sdl::Color(void) const; private: /// @brief Whether we're adding or subtracting from the color value. bool m_direction = true; + /// @brief Color value. uint8_t m_colorMod = 0; }; diff --git a/include/ui/render_functions.hpp b/include/ui/render_functions.hpp index 580b8ec..e925401 100644 --- a/include/ui/render_functions.hpp +++ b/include/ui/render_functions.hpp @@ -1,5 +1,6 @@ #pragma once #include "sdl.hpp" +#include "ui/ColorMod.hpp" // These are just functions to render generic parts of the UI. namespace ui @@ -19,5 +20,5 @@ namespace ui /// @param width Width of dialog box in pixels. /// @param height Height of dialog box in pixels. /// @param colorMod Color to multiply in rendering. - void render_bounding_box(SDL_Texture *target, int x, int y, int width, int height, uint8_t colorMod); + void render_bounding_box(SDL_Texture *target, int x, int y, int width, int height, const ui::ColorMod &colorMod); } // namespace ui diff --git a/source/appstates/BaseTask.cpp b/source/appstates/BaseTask.cpp new file mode 100644 index 0000000..88846c7 --- /dev/null +++ b/source/appstates/BaseTask.cpp @@ -0,0 +1,37 @@ +#include "appstates/BaseTask.hpp" +#include "colors.hpp" + +namespace +{ + /// @brief This is the time in milliseconds between changing glyphs. + constexpr uint64_t TICKS_GLYPH_TRIGGER = 50; +} // namespace + +BaseTask::BaseTask(void) : AppState(false) +{ + m_frameTimer.start(TICKS_GLYPH_TRIGGER); +} + +void BaseTask::update(void) +{ + // Just bail if the timer wasn't triggered yet. + if (!m_frameTimer.is_triggered()) + { + return; + } + + // Reset to 0 here. + if (++m_currentFrame >= 8) + { + m_currentFrame = 0; + } + + // Update the color pulse. + m_colorMod.update(); +} + +void BaseTask::render_loading_glyph(void) +{ + // This assumes it's being called after the background was dimmed. + sdl::text::render(NULL, 56, 673, 32, sdl::text::NO_TEXT_WRAP, m_colorMod, sm_glyphArray.at(m_currentFrame).data()); +} diff --git a/source/appstates/ProgressState.cpp b/source/appstates/ProgressState.cpp index 2b840b4..4a8ee0c 100644 --- a/source/appstates/ProgressState.cpp +++ b/source/appstates/ProgressState.cpp @@ -10,6 +10,9 @@ void ProgressState::update(void) { + // Base routine. + BaseTask::update(); + if (m_task.is_running() && input::button_pressed(HidNpadButton_Plus)) { ui::PopMessageManager::push_message(ui::PopMessageManager::DEFAULT_MESSAGE_TICKS, @@ -44,4 +47,7 @@ void ProgressState::render(void) colors::WHITE, "%s%%", m_percentageString.c_str()); + + // Glyph in the corner. + BaseTask::render_loading_glyph(); } diff --git a/source/appstates/TaskState.cpp b/source/appstates/TaskState.cpp index 2977cce..9bf7b6b 100644 --- a/source/appstates/TaskState.cpp +++ b/source/appstates/TaskState.cpp @@ -7,6 +7,9 @@ void TaskState::update(void) { + // Run the base update routine. + BaseTask::update(); + if (m_task.is_running() && input::button_pressed(HidNpadButton_Plus)) { // Throw the message. @@ -29,4 +32,6 @@ void TaskState::render(void) sdl::render_rect_fill(NULL, 0, 0, 1280, 720, colors::DIM_BACKGROUND); // Render the status. sdl::text::render(NULL, statusX, 351, 24, sdl::text::NO_TEXT_WRAP, colors::WHITE, status.c_str()); + // Render the loading glyph + BaseTask::render_loading_glyph(); } diff --git a/source/ui/ColorMod.cpp b/source/ui/ColorMod.cpp index 8e43dd8..badd7b7 100644 --- a/source/ui/ColorMod.cpp +++ b/source/ui/ColorMod.cpp @@ -12,7 +12,7 @@ void ui::ColorMod::update(void) } } -ui::ColorMod::operator uint8_t(void) const +ui::ColorMod::operator sdl::Color(void) const { - return m_colorMod; + return {static_cast((0x88 + m_colorMod) << 16 | (0xC5 + (m_colorMod / 2)) << 8 | 0xFF)}; } diff --git a/source/ui/render_functions.cpp b/source/ui/render_functions.cpp index cb0996f..ca8acd9 100644 --- a/source/ui/render_functions.cpp +++ b/source/ui/render_functions.cpp @@ -27,7 +27,7 @@ void ui::render_dialog_box(SDL_Texture *target, int x, int y, int width, int hei s_dialogCorners->render_part(NULL, (x + width) - 16, (y + height) - 16, 16, 16, 16, 16); } -void ui::render_bounding_box(SDL_Texture *target, int x, int y, int width, int height, uint8_t colorMod) +void ui::render_bounding_box(SDL_Texture *target, int x, int y, int width, int height, const ui::ColorMod &colorMod) { if (!s_menuBoundingCorners) { @@ -35,21 +35,18 @@ void ui::render_bounding_box(SDL_Texture *target, int x, int y, int width, int h sdl::TextureManager::create_load_texture("MenuBoundingCorners", "romfs:/Textures/MenuBounding.png"); } - // Setup color. - sdl::Color renderMod = {static_cast((0x88 + colorMod) << 16 | (0xC5 + (colorMod / 2)) << 8 | 0xFF)}; - // This shouldn't fail, but I don't really care if it does. - s_menuBoundingCorners->set_color_mod(renderMod); + s_menuBoundingCorners->set_color_mod(colorMod); // Top s_menuBoundingCorners->render_part(target, x, y, 0, 0, 8, 8); - sdl::render_rect_fill(target, x + 8, y, width - 16, 4, renderMod); + sdl::render_rect_fill(target, x + 8, y, width - 16, 4, colorMod); s_menuBoundingCorners->render_part(target, (x + width) - 8, y, 8, 0, 8, 8); // Middle - sdl::render_rect_fill(target, x, y + 8, 4, height - 16, renderMod); - sdl::render_rect_fill(target, (x + width) - 4, y + 8, 4, height - 16, renderMod); + sdl::render_rect_fill(target, x, y + 8, 4, height - 16, colorMod); + sdl::render_rect_fill(target, (x + width) - 4, y + 8, 4, height - 16, colorMod); // Bottom s_menuBoundingCorners->render_part(target, x, (y + height) - 8, 0, 8, 8, 8); - sdl::render_rect_fill(target, x + 8, (y + height) - 4, width - 16, 4, renderMod); + sdl::render_rect_fill(target, x + 8, (y + height) - 4, width - 16, 4, colorMod); s_menuBoundingCorners->render_part(target, (x + width) - 8, (y + height) - 8, 8, 8, 8, 8); }