Refactor and migrate ui::Menu to use ui::Transition.

This commit is contained in:
J-D-K 2025-10-04 19:39:34 -04:00
parent 15d7063871
commit f3cd2969bf
16 changed files with 163 additions and 74 deletions

View File

@ -4,6 +4,7 @@
#include "ui/ColorMod.hpp"
#include "ui/Element.hpp"
#include "ui/TextScroll.hpp"
#include "ui/Transition.hpp"
#include <memory>
#include <string>
@ -23,9 +24,6 @@ namespace ui
/// @param renderTargetHeight Height of the render target to calculate option height and scrolling.
Menu(int x, int y, int width, int fontSize, int renderTargetHeight);
/// @brief Required destructor.
~Menu() {};
/// @brief Creates and returns a new ui::Menu instance.
static inline std::shared_ptr<ui::Menu> create(int x, int y, int width, int fontSize, int renderTargetHeight)
{
@ -83,6 +81,9 @@ namespace ui
/// @brief Y coordinate menu is rendered to.
double m_y{};
/// @brief This is used for scrolling the menu.
ui::Transition m_transition{};
/// @brief Currently selected option.
int m_selected{};
@ -99,9 +100,6 @@ namespace ui
/// @brief This to preserve the original Y coordinate passed.
double m_originalY{};
/// @brief The target Y coordinate the menu should be rendered at.
double m_targetY{};
/// @brief Maximum number of display options render target can show.
int m_maxDisplayOptions{};
@ -126,8 +124,17 @@ namespace ui
/// @brief Text scroll for when the current option is too long to on screen.
std::shared_ptr<ui::TextScroll> m_optionScroll{};
/// @brief Keeps track of the current menu ID for render target.
static inline int sm_menuID{};
/// @brief Calculates the alignment variables.
void calculate_alignments() noexcept;
/// @brief Initializes transition.
void initialize_transition() noexcept;
/// @brief Creates the option target for the menu.
void initialize_option_target();
/// @brief Initializes the UI elements used within the menu.
void initialize_ui_elements();
/// @brief Updates the text scroll for the currently highlighted option.
void update_scroll_text();

View File

@ -59,8 +59,11 @@ namespace ui
/// @brief This times updates the end of the substr printed.
sys::Timer m_typeTimer{};
/// @brief Dialog used to render the background.
std::shared_ptr<ui::DialogBox> m_dialog{};
/// @brief This is the texture used for the ends of the messages.
static inline sdl::SharedTexture sm_endCaps{};
/// @brief Ensures ^ is loaded and ready to go.
void initialize_static_members();
/// @brief Updates the Y Coord to match the target passed.
void update_y(double targetY) noexcept;
@ -71,6 +74,9 @@ namespace ui
/// @brief Updates the width of the dialog.
void update_width() noexcept;
/// @brief Renders the container around the message.
void render_container() noexcept;
/// @brief Signals the pop message to close.
void close() noexcept;
};

View File

@ -6,7 +6,7 @@ namespace ui
{
public:
/// @brief Default.
Transition() = default;
Transition();
/// @brief Constructs a new transition.
/// @param x Beginning X coord.
@ -96,6 +96,11 @@ namespace ui
/// @brief Sets the target height of the transition.
void set_target_height(int targetHeight) noexcept;
/// @brief Sets the threshold before snapping occurs.
void set_threshold(int threshold) noexcept;
static inline int DEFAULT_THRESHOLD = 2;
private:
/// @brief Current X.
double m_x{};

Binary file not shown.

After

Width:  |  Height:  |  Size: 736 B

View File

@ -15,7 +15,7 @@ FileModeState::FileModeState(std::string_view mountA, std::string_view mountB, i
: m_mountA(mountA)
, m_mountB(mountB)
, m_journalSize(journalSize)
, m_transition(15, 720, 0, 0, 15, 85, 0, 0, 4)
, m_transition(15, 720, 0, 0, 15, 87, 0, 0, ui::Transition::DEFAULT_THRESHOLD)
, m_isSystem(isSystem)
, m_allowSystem(config::get_by_key(config::keys::ALLOW_WRITING_TO_SYSTEM))
{
@ -101,7 +101,7 @@ void FileModeState::initialize_paths()
void FileModeState::initialize_menus()
{
m_dirMenuA = ui::Menu::create(8, 5, 594, 20, 538);
m_dirMenuB = ui::Menu::create(626, 5, 594, 20, 538);
m_dirMenuB = ui::Menu::create(630, 5, 594, 20, 538);
FileModeState::initialize_directory_menu(m_pathA, m_dirA, *m_dirMenuA.get());
FileModeState::initialize_directory_menu(m_pathB, m_dirB, *m_dirMenuB.get());
@ -123,9 +123,7 @@ void FileModeState::initialize_directory_menu(const fslib::Path &path, fslib::Di
{
std::string option{};
if (entry.is_directory()) { option = DIR_PREFIX; }
else {
option = FILE_PREFIX;
}
else { option = FILE_PREFIX; }
option += entry.get_filename();
menu.add_option(option);

View File

@ -42,7 +42,15 @@ static std::string get_size_string(int64_t totalSize);
FileOptionState::FileOptionState(FileModeState *spawningState)
: m_spawningState(spawningState)
, m_target(spawningState->m_target)
, m_transition(m_target ? RIGHT_X : LEFT_X, 232, 32, 32, m_target ? RIGHT_X : LEFT_X, 232, 256, 256, 4)
, m_transition(m_target ? RIGHT_X : LEFT_X,
232,
32,
32,
m_target ? RIGHT_X : LEFT_X,
232,
256,
256,
ui::Transition::DEFAULT_THRESHOLD)
, m_dataStruct(std::make_shared<FileOptionState::DataStruct>())
{
FileOptionState::initialize_static_members();

View File

@ -8,7 +8,7 @@
MessageState::MessageState(std::string_view message)
: m_message(message)
, m_transition(0, 0, 32, 32, 0, 0, 720, 256, 4)
, m_transition(0, 0, 32, 32, 0, 0, 720, 256, ui::Transition::DEFAULT_THRESHOLD)
{
MessageState::initialize_static_members();
}

View File

@ -22,7 +22,7 @@ namespace
}
ProgressState::ProgressState(sys::threadpool::JobFunction function, sys::Task::TaskData taskData)
: m_transition(0, 0, 32, 32, 0, 0, 720, 256, 4)
: m_transition(0, 0, 32, 32, 0, 0, 720, 256, ui::Transition::DEFAULT_THRESHOLD)
{
initialize_static_members();
m_task = std::make_unique<sys::ProgressTask>(function, taskData);

View File

@ -5,7 +5,7 @@
int main(int argc, const char *argv[])
{
// Store the pointers here so JKSV can update itself from where ever the use might have placed it.
// Store the pointers here so JKSV can update itself from where ever the user might have placed it.
cmdargs::store(argc, argv);
JKSV jksv{};

View File

@ -8,7 +8,7 @@ ui::ControlGuide::ControlGuide(const char *guide)
, m_textWidth(sdl::text::get_width(23, m_guide))
, m_targetX(1220 - (m_textWidth + 24))
, m_guideWidth(1280 - m_targetX)
, m_transition(1280, 662, 0, 0, m_targetX, 662, 0, 0, 4)
, m_transition(1280, 662, 0, 0, m_targetX, 662, 0, 0, ui::Transition::DEFAULT_THRESHOLD)
{
ui::ControlGuide::initialize_static_members();
}

View File

@ -24,7 +24,8 @@ void ui::IconMenu::update(bool hasFocus) { Menu::update(hasFocus); }
void ui::IconMenu::render(sdl::SharedTexture &target, bool hasFocus)
{
const int optionCount = m_options.size();
for (int i = 0, tempY = m_y; i < optionCount; i++, tempY += m_optionHeight)
const int y = m_transition.get_y();
for (int i = 0, tempY = y; i < optionCount; i++, tempY += m_optionHeight)
{
// Clear target.
m_optionTarget->clear(colors::TRANSPARENT);

View File

@ -11,20 +11,16 @@
ui::Menu::Menu(int x, int y, int width, int fontSize, int renderTargetHeight)
: m_x(x)
, m_y(y)
, m_optionHeight(std::floor(static_cast<double>(fontSize) * 1.8f))
, m_optionTarget(
sdl::TextureManager::load("MENU_" + std::to_string(sm_menuID++), width, m_optionHeight, SDL_TEXTUREACCESS_TARGET))
, m_boundingBox(ui::BoundingBox::create(0, 0, width + 12, m_optionHeight + 12))
, m_originalY(y)
, m_targetY(y)
, m_maxDisplayOptions((renderTargetHeight - m_originalY) / m_optionHeight)
, m_scrollLength(std::floor(static_cast<double>(m_maxDisplayOptions) / 2))
, m_width(width)
, m_fontSize(fontSize)
, m_textY((m_optionHeight / 2) - (m_fontSize / 2)) // This seems to be the best alignment.
, m_renderTargetHeight(renderTargetHeight)
, m_optionScroll(
ui::TextScroll::create({}, 16, 0, m_width, m_optionHeight, m_fontSize, colors::BLUE_GREEN, colors::TRANSPARENT)) {};
{
Menu::calculate_alignments();
Menu::initialize_transition();
Menu::initialize_option_target();
Menu::initialize_ui_elements();
}
void ui::Menu::update(bool hasFocus)
{
@ -36,6 +32,7 @@ void ui::Menu::update(bool hasFocus)
Menu::handle_input();
Menu::update_scrolling();
Menu::update_scroll_text();
m_transition.update();
}
void ui::Menu::render(sdl::SharedTexture &target, bool hasFocus)
@ -44,7 +41,8 @@ void ui::Menu::render(sdl::SharedTexture &target, bool hasFocus)
// I hate doing this.
const int optionSize = m_options.size();
for (int i = 0, tempY = m_y; i < optionSize; i++, tempY += m_optionHeight)
const int y = m_transition.get_y();
for (int i = 0, tempY = y; i < optionSize; i++, tempY += m_optionHeight)
{
if (tempY < -m_fontSize) { continue; }
else if (tempY > m_renderTargetHeight) { break; }
@ -114,6 +112,48 @@ void ui::Menu::reset(bool full)
bool ui::Menu::is_empty() const noexcept { return m_options.empty(); }
void ui::Menu::calculate_alignments() noexcept
{
m_optionHeight = std::floor(static_cast<double>(m_fontSize) * 1.8f);
m_maxDisplayOptions = (m_renderTargetHeight - m_originalY) / m_optionHeight;
m_scrollLength = std::floor(static_cast<double>(m_maxDisplayOptions) / 2.0f);
m_textY = (m_optionHeight / 2) - (m_fontSize / 2);
}
void ui::Menu::initialize_transition() noexcept
{
m_transition.set_x(m_x);
m_transition.set_y(m_y);
m_transition.set_target_x(m_x);
m_transition.set_target_y(m_y);
m_transition.set_threshold(m_transition.DEFAULT_THRESHOLD);
}
void ui::Menu::initialize_option_target()
{
static int MENU_ID{};
const std::string optionTargetName = "MENU_TARGET_" + std::to_string(MENU_ID++);
m_optionTarget = sdl::TextureManager::load(optionTargetName, m_width, m_optionHeight, SDL_TEXTUREACCESS_TARGET);
}
void ui::Menu::initialize_ui_elements()
{
// This is empty by default.
static constexpr std::string_view EMPTY = {};
m_boundingBox = ui::BoundingBox::create(0, 0, m_width + 12, m_optionHeight + 12);
m_optionScroll = ui::TextScroll::create(EMPTY,
16,
0,
m_width,
m_optionHeight,
m_fontSize,
colors::BLUE_GREEN,
colors::TRANSPARENT,
false);
}
void ui::Menu::update_scroll_text()
{
const std::string_view text = m_optionScroll->get_text();
@ -149,17 +189,12 @@ void ui::Menu::update_scrolling()
const int endScrollPoint = optionsSize - (m_maxDisplayOptions - m_scrollLength);
const int scrolledItems = m_selected - m_scrollLength;
const double scaling = config::get_animation_scaling();
if (m_selected < m_scrollLength) { m_targetY = m_originalY; } // Don't bother. There's no point.
else if (m_selected >= endScrollPoint) { m_targetY = m_originalY - (optionsSize - m_maxDisplayOptions) * m_optionHeight; }
else if (m_selected >= m_scrollLength) { m_targetY = m_originalY - (scrolledItems * m_optionHeight); }
int targetY{};
const int previousTarget = m_transition.get_target_y();
if (m_selected < m_scrollLength) { targetY = m_originalY; } // Don't bother. There's no point.
else if (m_selected >= endScrollPoint) { targetY = m_originalY - (optionsSize - m_maxDisplayOptions) * m_optionHeight; }
else if (m_selected >= m_scrollLength) { targetY = m_originalY - (scrolledItems * m_optionHeight); }
if (m_y != m_targetY)
{
m_y += std::round((m_targetY - m_y) / scaling);
const int distance = math::Util<double>::absolute_distance(m_y, m_targetY);
if (distance <= 2) { m_y = m_targetY; }
}
if (targetY != previousTarget) { m_transition.set_target_y(targetY); }
}

View File

@ -7,23 +7,28 @@
namespace
{
static inline constexpr int START_X = 624;
static inline constexpr double START_Y = 720;
static inline constexpr int START_WIDTH = 32;
static inline constexpr int PERMA_HEIGHT = 48;
constexpr int START_X = 624;
constexpr double START_Y = 720;
constexpr int START_WIDTH = 20;
constexpr int PERMA_HEIGHT = 48;
constexpr int TRANSITION_THRESHOLD = 2; // This is used so the easing on opening doesn't look so odd.
}
ui::PopMessage::PopMessage(int ticks, std::string_view message)
: m_transition(START_X, START_Y, START_WIDTH, PERMA_HEIGHT, 0, 0, 0, 0, 4)
: m_transition(START_X, START_Y, START_WIDTH, PERMA_HEIGHT, 0, 0, 0, 0, TRANSITION_THRESHOLD)
, m_ticks(ticks)
, m_message(message)
, m_dialog(ui::DialogBox::create(START_X, START_Y - 6, START_WIDTH, PERMA_HEIGHT, ui::DialogBox::Type::Light)) {};
{
PopMessage::initialize_static_members();
}
ui::PopMessage::PopMessage(int ticks, std::string &message)
: m_transition(START_X, START_Y, START_WIDTH, PERMA_HEIGHT, 0, 0, 0, 0, 4)
: m_transition(START_X, START_Y, START_WIDTH, PERMA_HEIGHT, 0, 0, 0, 0, TRANSITION_THRESHOLD)
, m_ticks(ticks)
, m_message(std::move(message))
, m_dialog(ui::DialogBox::create(START_X, START_Y - 6, START_WIDTH, PERMA_HEIGHT, ui::DialogBox::Type::Light)) {};
{
PopMessage::initialize_static_members();
}
void ui::PopMessage::update(double targetY)
{
@ -37,19 +42,29 @@ void ui::PopMessage::update(double targetY)
void ui::PopMessage::render()
{
m_dialog->render(sdl::Texture::Null, false);
PopMessage::render_container();
if (!m_yMet || m_close) { return; }
// This avoids allocating and returning another std::string.
const int y = m_transition.get_y();
const std::string_view message(m_message.c_str(), m_substrOffset);
sdl::text::render(sdl::Texture::Null, m_textX, y + 5, 22, sdl::text::NO_WRAP, colors::BLACK, message);
sdl::text::render(sdl::Texture::Null, m_textX, y + 11, 22, sdl::text::NO_WRAP, colors::BLACK, message);
}
bool ui::PopMessage::finished() const noexcept { return m_finished; }
std::string_view ui::PopMessage::get_message() const noexcept { return m_message; }
void ui::PopMessage::initialize_static_members()
{
static constexpr std::string_view TEX_CAP_NAME = "PopCaps";
static constexpr const char *TEX_CAP_PATH = "romfs:/Textures/PopMessage.png";
if (sm_endCaps) { return; }
sm_endCaps = sdl::TextureManager::load(TEX_CAP_NAME, TEX_CAP_PATH);
}
void ui::PopMessage::update_y(double targetY) noexcept
{
if (!m_close) { m_transition.set_target_y(targetY); };
@ -60,9 +75,6 @@ void ui::PopMessage::update_y(double targetY) noexcept
m_yMet = true;
m_typeTimer.start(5);
}
const int y = m_transition.get_y();
m_dialog->set_y(y - 6);
}
void ui::PopMessage::update_text_offset()
@ -84,12 +96,8 @@ void ui::PopMessage::update_text_offset()
const int stringWidth = sdl::text::get_width(22, subMessage);
m_textX = HALF_WIDTH - (stringWidth / 2);
const int dialogX = m_textX - 16;
const int dialogWidth = stringWidth + 32;
m_transition.set_target_x(dialogX);
m_transition.set_target_width(dialogWidth);
if (m_substrOffset >= messageLength) { m_displayTimer.start(m_ticks); }
}
@ -99,17 +107,25 @@ void ui::PopMessage::update_width() noexcept
m_transition.update_width_height();
const int x = m_transition.get_centered_x();
const int width = m_transition.get_width();
m_dialog->set_x(x);
m_dialog->set_width(width);
if (m_close && m_transition.in_place_width_height()) { m_transition.set_target_y(720); }
}
void ui::PopMessage::render_container() noexcept
{
constexpr int WIDTH = 48;
constexpr int HALF = WIDTH / 2;
const int x = m_transition.get_centered_x() - (HALF / 2);
const int y = m_transition.get_y();
const int width = m_transition.get_width() + HALF;
sm_endCaps->render_part(sdl::Texture::Null, x, y, 0, 0, HALF, WIDTH);
sdl::render_rect_fill(sdl::Texture::Null, x + HALF, y, width - WIDTH, 48, colors::DIALOG_LIGHT);
sm_endCaps->render_part(sdl::Texture::Null, x + (width - HALF), y, HALF, 0, HALF, 48);
}
void ui::PopMessage::close() noexcept
{
m_close = true;
m_transition.set_target_width(32);
m_transition.set_target_width(START_WIDTH);
}

View File

@ -72,7 +72,7 @@ void ui::PopMessageManager::push_message(int displayTicks, std::string_view mess
{
ui::PopMessage &back = messages.back();
const std::string_view lastMessage = back.get_message();
if (lastMessage == message) { return; }
// if (lastMessage == message) { return; }
}
}

View File

@ -16,7 +16,15 @@ ui::SlideOutPanel::SlideOutPanel(int width, Side side)
: m_width(width)
, m_targetX(side == Side::Left ? 0.0f : static_cast<double>(SCREEN_WIDTH) - m_width)
, m_side(side)
, m_transition(m_side == Side::Left ? -m_width : SCREEN_WIDTH, 0, 0, 0, m_targetX, 0, 0, 0, 4)
, m_transition(m_side == Side::Left ? -m_width : SCREEN_WIDTH,
0,
0,
0,
m_targetX,
0,
0,
0,
ui::Transition::DEFAULT_THRESHOLD)
, m_renderTarget(
sdl::TextureManager::load("PANEL_" + std::to_string(sm_targetID++), m_width, 720, SDL_TEXTUREACCESS_TARGET)) {};

View File

@ -6,6 +6,9 @@
#include <cmath>
ui::Transition::Transition()
: m_scaling(config::get_animation_scaling()) {};
ui::Transition::Transition(int x,
int y,
int width,
@ -83,13 +86,15 @@ void ui::Transition::set_width(int width) noexcept { m_width = static_cast<doubl
void ui::Transition::set_height(int height) noexcept { m_height = static_cast<double>(height); }
void ui::Transition::set_target_x(int targetX) noexcept { m_targetX = targetX; }
void ui::Transition::set_target_x(int targetX) noexcept { m_targetX = static_cast<double>(targetX); }
void ui::Transition::set_target_y(int targetY) noexcept { m_targetY = targetY; }
void ui::Transition::set_target_y(int targetY) noexcept { m_targetY = static_cast<double>(targetY); }
void ui::Transition::set_target_width(int targetWidth) noexcept { m_targetWidth = targetWidth; }
void ui::Transition::set_target_width(int targetWidth) noexcept { m_targetWidth = static_cast<double>(targetWidth); }
void ui::Transition::set_target_height(int targetHeight) noexcept { m_targetHeight = targetHeight; }
void ui::Transition::set_target_height(int targetHeight) noexcept { m_targetHeight = static_cast<double>(targetHeight); }
void ui::Transition::set_threshold(int threshold) noexcept { m_threshold = static_cast<double>(threshold); }
void ui::Transition::update_x_coord() noexcept
{