Tweak SlidePanel behavior.

This commit is contained in:
J-D-K 2025-07-29 19:06:48 -04:00
parent bc3f028082
commit 58e6217e73
9 changed files with 131 additions and 117 deletions

View File

@ -1,6 +1,7 @@
#pragma once
#include "sdl.hpp"
#include "ui/Element.hpp"
#include <vector>
namespace ui
@ -47,7 +48,7 @@ namespace ui
/// @brief Returns if the panel is fully closed.
/// @return If the panel is fully closed.
bool is_closed() const;
bool is_closed();
/// @brief Pushes a new element to the element vector.
/// @param newElement New element to push.
@ -62,27 +63,27 @@ namespace ui
private:
/// @brief Bool for whether panel is fully open or not.
bool m_isOpen = false;
bool m_isOpen{};
/// @brief Whether or not to close panel.
bool m_closePanel = false;
bool m_closePanel{};
/// @brief Current X coordinate to render to. Panels are always 720 pixels in height so no Y is required.
double m_x;
double m_x{};
/// @brief Width of the panel in pixels.
int m_width;
int m_width{};
/// @brief Target X position of panel.
double m_targetX;
double m_targetX{};
/// @brief Which side the panel is on.
SlideOutPanel::Side m_side;
SlideOutPanel::Side m_side{};
/// @brief Render target if panel.
sdl::SharedTexture m_renderTarget;
sdl::SharedTexture m_renderTarget{};
/// @brief Vector of elements.
std::vector<std::shared_ptr<ui::Element>> m_elements;
std::vector<std::shared_ptr<ui::Element>> m_elements{};
};
} // namespace ui

View File

@ -90,7 +90,7 @@
],
"SaveCreatePops": [
"0: Save data created for #%s#!",
"1: Error creating save data for #%s#!",
"1: Error creating save data!",
"2: Error deleting save data!"
],
"SaveDataTypes": [

View File

@ -122,17 +122,19 @@ static bool compare_info(data::TitleInfo *infoA, data::TitleInfo *infoB)
const char *titleA = infoA->get_title();
const char *titleB = infoB->get_title();
size_t titleALength = std::char_traits<char>::length(titleA);
size_t titleBLength = std::char_traits<char>::length(titleB);
size_t shortestTitle = titleALength < titleBLength ? titleALength : titleBLength;
const size_t titleALength = std::char_traits<char>::length(titleA);
const size_t titleBLength = std::char_traits<char>::length(titleB);
const size_t shortestTitle = titleALength < titleBLength ? titleALength : titleBLength;
// To do: This doesn't take into account which is the shortest title. This can still go out-of-bounds.
for (size_t i = 0, j = 0; i < shortestTitle;)
{
uint32_t codepointA = 0;
uint32_t codepointB = 0;
ssize_t unitCountA = decode_utf8(&codepointA, reinterpret_cast<const uint8_t *>(&titleA[i]));
ssize_t unitCountB = decode_utf8(&codepointB, reinterpret_cast<const uint8_t *>(&titleB[j]));
const uint8_t *pointA = reinterpret_cast<const uint8_t *>(&titleA[i]);
const uint8_t *pointB = reinterpret_cast<const uint8_t *>(&titleB[j]);
const ssize_t unitCountA = decode_utf8(&codepointA, pointA);
const ssize_t unitCountB = decode_utf8(&codepointB, pointB);
if (unitCountA <= 0 || unitCountB <= 0) { return false; }

View File

@ -70,7 +70,7 @@ void TitleInfoState::initialize_static_members()
{
if (!sm_slidePanel)
{
sm_slidePanel = std::make_unique<ui::SlideOutPanel>(SIZE_PANEL_WIDTH, ui::SlideOutPanel::Side::Right);
sm_slidePanel = std::make_unique<ui::SlideOutPanel>(SIZE_PANEL_WIDTH, ui::SlideOutPanel::Side::Left);
}
}

View File

@ -282,15 +282,16 @@ static void change_output_path(data::TitleInfo *targetTitle)
static constexpr size_t SIZE_PATH_BUFFER = 0x200;
const char *headerTemplate = strings::get_by_name(strings::names::KEYBOARD, 7);
const int popTicks = ui::PopMessageManager::DEFAULT_TICKS;
const char *popSuccess = strings::get_by_name(strings::names::TITLEOPTION_POPS, 1);
const char *popFailure = strings::get_by_name(strings::names::TITLEOPTION_POPS, 0);
const char *popSuccess = strings::get_by_name(strings::names::TITLEOPTION_POPS, 8);
const char *popFailure = strings::get_by_name(strings::names::TITLEOPTION_POPS, 9);
const char *pathSafeTitle = targetTitle->get_path_safe_title();
const std::string headerString = stringutil::get_formatted_string(headerTemplate, targetTitle->get_title());
char pathBuffer[SIZE_PATH_BUFFER] = {0};
const bool inputIsValid = keyboard::get_input(SwkbdType_QWERTY, pathSafeTitle, headerString, pathBuffer, SIZE_PATH_BUFFER);
const bool sanitized = inputIsValid && stringutil::sanitize_string_for_path(pathBuffer, pathBuffer, SIZE_PATH_BUFFER);
if (!inputIsValid || !sanitized)
const bool notEmpty = std::char_traits<char>::length(pathBuffer) > 0;
if (!inputIsValid || !sanitized || !notEmpty)
{
ui::PopMessageManager::push_message(popTicks, popFailure);
return;

View File

@ -1,6 +1,7 @@
#include "strings.hpp"
#include "JSON.hpp"
#include "error.hpp"
#include "fslib.hpp"
#include "stringutil.hpp"
@ -33,41 +34,9 @@ namespace
{SetLanguage_PTBR, "PTBR.json"}};
} // namespace
// This returns the language file to use depending on the system's language.
static fslib::Path get_file_path()
{
fslib::Path returnPath = "romfs:/Text";
uint64_t languageCode = 0;
Result setError = setGetLanguageCode(&languageCode);
if (R_FAILED(setError)) { return returnPath / s_fileMap.at(SetLanguage_ENUS); }
SetLanguage language;
setError = setMakeLanguage(languageCode, &language);
if (R_FAILED(setError)) { return returnPath / s_fileMap.at(SetLanguage_ENUS); }
return returnPath / s_fileMap.at(language);
}
static void replace_buttons_in_string(std::string &target)
{
stringutil::replace_in_string(target, "[A]", "\ue0e0");
stringutil::replace_in_string(target, "[B]", "\ue0e1");
stringutil::replace_in_string(target, "[X]", "\ue0e2");
stringutil::replace_in_string(target, "[Y]", "\ue0e3");
stringutil::replace_in_string(target, "[L]", "\ue0e4");
stringutil::replace_in_string(target, "[R]", "\ue0e5");
stringutil::replace_in_string(target, "[ZL]", "\ue0e6");
stringutil::replace_in_string(target, "[ZR]", "\ue0e7");
stringutil::replace_in_string(target, "[SL]", "\ue0e8");
stringutil::replace_in_string(target, "[SR]", "\ue0e9");
stringutil::replace_in_string(target, "[DPAD]", "\ue0ea");
stringutil::replace_in_string(target, "[DUP]", "\ue0eb");
stringutil::replace_in_string(target, "[DDOWN]", "\ue0ec");
stringutil::replace_in_string(target, "[DLEFT]", "\ue0ed");
stringutil::replace_in_string(target, "[DRIGHT]", "\ue0ee");
stringutil::replace_in_string(target, "[+]", "\ue0ef");
stringutil::replace_in_string(target, "[-]", "\ue0f0");
}
// Definitions at bottom.
static fslib::Path get_file_path();
static void replace_buttons_in_string(std::string &target);
bool strings::initialize()
{
@ -90,9 +59,9 @@ bool strings::initialize()
{
json_object *string = json_object_array_get_idx(array, i);
std::string_view slicer = json_object_get_string(string);
const int mapIndex = i;
const auto mapPair = std::make_pair(name, mapIndex);
const size_t begin = slicer.find(": ");
const auto mapPair = std::make_pair(name, i);
const size_t begin = slicer.find(": ");
if (begin != slicer.npos) { slicer = slicer.substr(begin + 2); }
s_stringMap[mapPair] = slicer;
@ -114,3 +83,40 @@ const char *strings::get_by_name(std::string_view name, int index)
if (findPair == s_stringMap.end()) { return nullptr; }
return s_stringMap.at(mapPair).c_str();
}
static fslib::Path get_file_path()
{
static constexpr std::string_view PATH_BASE = "romfs:/Text";
fslib::Path returnPath{PATH_BASE};
uint64_t languageCode{};
SetLanguage language{};
const bool codeError = error::libnx(setGetLanguageCode(&languageCode));
const bool langError = !codeError && error::libnx(setMakeLanguage(languageCode, &language));
if (codeError || langError) { returnPath /= s_fileMap[SetLanguage_ENUS]; }
else { returnPath /= s_fileMap[language]; }
return returnPath;
}
static void replace_buttons_in_string(std::string &target)
{
stringutil::replace_in_string(target, "[A]", "\ue0e0");
stringutil::replace_in_string(target, "[B]", "\ue0e1");
stringutil::replace_in_string(target, "[X]", "\ue0e2");
stringutil::replace_in_string(target, "[Y]", "\ue0e3");
stringutil::replace_in_string(target, "[L]", "\ue0e4");
stringutil::replace_in_string(target, "[R]", "\ue0e5");
stringutil::replace_in_string(target, "[ZL]", "\ue0e6");
stringutil::replace_in_string(target, "[ZR]", "\ue0e7");
stringutil::replace_in_string(target, "[SL]", "\ue0e8");
stringutil::replace_in_string(target, "[SR]", "\ue0e9");
stringutil::replace_in_string(target, "[DPAD]", "\ue0ea");
stringutil::replace_in_string(target, "[DUP]", "\ue0eb");
stringutil::replace_in_string(target, "[DDOWN]", "\ue0ec");
stringutil::replace_in_string(target, "[DLEFT]", "\ue0ed");
stringutil::replace_in_string(target, "[DRIGHT]", "\ue0ee");
stringutil::replace_in_string(target, "[+]", "\ue0ef");
stringutil::replace_in_string(target, "[-]", "\ue0f0");
}

View File

@ -5,6 +5,7 @@
#include <cstdarg>
#include <cstring>
#include <ctime>
#include <string>
#include <switch.h>
namespace
@ -18,72 +19,63 @@ namespace
std::string stringutil::get_formatted_string(const char *format, ...)
{
char vaBuffer[VA_BUFFER_SIZE] = {0};
std::array<char, VA_BUFFER_SIZE> vaBuffer = {0};
std::va_list vaList;
va_start(vaList, format);
vsnprintf(vaBuffer, VA_BUFFER_SIZE, format, vaList);
vsnprintf(vaBuffer.data(), VA_BUFFER_SIZE, format, vaList);
va_end(vaList);
return std::string(vaBuffer);
return std::string(vaBuffer.data());
}
void stringutil::replace_in_string(std::string &target, std::string_view find, std::string_view replace)
{
size_t stringPosition = 0;
while ((stringPosition = target.find(find, stringPosition)) != target.npos)
const size_t findLength = find.length();
const size_t replaceLength = replace.length();
for (size_t i = target.find(find); i != target.npos; i = target.find(find, i + replaceLength))
{
target.replace(stringPosition, find.length(), replace);
target.replace(i, findLength, replace);
}
}
void stringutil::strip_character(char c, std::string &target)
{
size_t charPosition = 0;
while ((charPosition = target.find_first_of(c, charPosition)) != target.npos)
for (size_t i = target.find_first_of(c); i != target.npos; i = target.find_first_of(c, i))
{
target.erase(target.begin() + charPosition);
target.erase(target.begin() + i);
}
}
bool stringutil::sanitize_string_for_path(const char *stringIn, char *stringOut, size_t stringOutSize)
{
uint32_t codepoint = 0;
size_t stringLength = std::strlen(stringIn);
for (size_t i = 0, stringOutOffset = 0; i < stringLength;)
uint32_t codepoint{};
const size_t length = std::char_traits<char>::length(stringIn);
for (size_t i = 0, offset = 0; i < length;)
{
ssize_t unitCount = decode_utf8(&codepoint, reinterpret_cast<const uint8_t *>(&stringIn[i]));
if (unitCount <= 0 || i + unitCount >= stringOutSize) { break; }
const uint8_t *point = reinterpret_cast<const uint8_t *>(&stringIn[i]);
const ssize_t count = decode_utf8(&codepoint, point);
const bool countCheck = count <= 0 || i + count >= stringOutSize;
const bool codeCheck = codepoint < 0x20 || codepoint > 0x7E;
if (countCheck) { break; }
else if (codeCheck) { return false; }
if (codepoint < 0x20 || codepoint > 0x7E)
{
// Don't even bother. It's not possible.
return false;
}
// replace forbidden with spaces.
if (std::find(FORBIDDEN_PATH_CHARACTERS.begin(), FORBIDDEN_PATH_CHARACTERS.end(), codepoint) !=
FORBIDDEN_PATH_CHARACTERS.end())
{
stringOut[stringOutOffset++] = 0x20;
}
else if (codepoint == L'é') { stringOut[stringOutOffset++] = 'e'; }
const bool forbidden = std::find(FORBIDDEN_PATH_CHARACTERS.begin(), FORBIDDEN_PATH_CHARACTERS.end(), codepoint) !=
FORBIDDEN_PATH_CHARACTERS.end();
if (forbidden) { stringOut[offset++] = 0x20; }
else if (codepoint == L'é') { stringOut[offset++] = 'e'; }
else
{
// Just memcpy it over. This is a safety thing to be honest. Since it's only Ascii allowed, unitcount should only
// be 1.
std::memcpy(&stringOut[stringOutOffset], &stringIn[i], static_cast<size_t>(unitCount));
stringOutOffset += unitCount;
std::memcpy(&stringOut[offset], &stringIn[i], static_cast<size_t>(count));
offset += count;
}
i += unitCount;
i += count;
}
// Loop backwards and trim off spaces and periods.
size_t stringOutLength = std::strlen(stringOut);
while (stringOut[stringOutLength - 1] == ' ' || stringOut[stringOutLength - 1] == '.')
{
stringOut[--stringOutLength] = 0x00;
}
const int outLength = std::char_traits<char>::length(stringOut) - 1;
for (int i = outLength; i > 0 && (stringOut[i] == ' ' || stringOut[i] == '.'); --i) { stringOut[i] = '\0'; }
return true;
}

View File

@ -5,10 +5,15 @@
#include <cmath>
namespace
{
constexpr int SCREEN_WIDTH = 1280;
}
ui::SlideOutPanel::SlideOutPanel(int width, Side side)
: m_x(side == Side::Left ? -width : 1280)
: m_x(side == Side::Left ? -width : SCREEN_WIDTH)
, m_width(width)
, m_targetX(side == Side::Left ? 0 : 1280 - m_width)
, m_targetX(side == Side::Left ? 0 : SCREEN_WIDTH - m_width)
, m_side(side)
{
static int slidePanelTargetID = 0;
@ -21,24 +26,18 @@ ui::SlideOutPanel::SlideOutPanel(int width, Side side)
void ui::SlideOutPanel::update(bool hasFocus)
{
double scaling = config::get_animation_scaling();
// The first two conditions are just a workaround because my math keeps leaving two pixels.
if (!m_isOpen && m_side == Side::Left && m_x >= -4)
const double scaling = config::get_animation_scaling();
const bool openingFromLeft = !m_isOpen && m_side == Side::Left && m_x < m_targetX;
const bool openingFromRight = !m_isOpen && m_side == Side::Right && m_x > m_targetX;
if (openingFromLeft) { m_x -= std::round(m_x / scaling); }
else if (openingFromRight)
{
m_x = 0;
m_isOpen = true;
}
else if (!m_isOpen && m_side == Side::Right && m_x - m_targetX <= 4)
{
m_x = 1280 - m_width;
m_isOpen = true;
}
else if (!m_isOpen && m_side == Side::Left && m_x != m_targetX) { m_x -= std::ceil(m_x / scaling); }
else if (!m_isOpen && m_side == Side::Right && m_x != m_targetX)
{
m_x += std::ceil((1280.0f - (static_cast<double>(m_width)) - m_x) / scaling);
const double screenWidth = static_cast<double>(SCREEN_WIDTH);
const double width = static_cast<double>(m_width);
const double pixels = (screenWidth - width - m_x) / scaling;
m_x += std::round(pixels);
}
else { m_isOpen = true; }
// I'm going to leave it to the individual elements whether they update if the state is active.
if (m_isOpen)
@ -58,7 +57,7 @@ void ui::SlideOutPanel::clear_target() { m_renderTarget->clear(colors::SLIDE_PAN
void ui::SlideOutPanel::reset()
{
m_x = m_side == Side::Left ? -(m_width) : 1280.0f;
m_x = m_side == Side::Left ? -(m_width) : SCREEN_WIDTH;
m_isOpen = false;
m_closePanel = false;
}
@ -67,7 +66,18 @@ void ui::SlideOutPanel::close() { m_closePanel = true; }
bool ui::SlideOutPanel::is_open() const { return m_isOpen; }
bool ui::SlideOutPanel::is_closed() const { return m_closePanel && (m_side == Side::Left ? m_x > -(m_width) : m_x < 1280); }
bool ui::SlideOutPanel::is_closed()
{
// I'm assuming this is going to be called, waiting for the panel to close so.
const double scaling = config::get_animation_scaling();
const bool closeToLeft = m_closePanel && m_side == Side::Left && m_x > -m_width;
const bool closeToRight = m_closePanel && m_side == Side::Right && m_x < SCREEN_WIDTH;
if (closeToLeft) { m_x += -(m_width - m_x) / scaling; }
else if (closeToRight) { m_x += m_x / scaling; }
const bool closed = m_side == Side::Left ? m_x <= -m_width : m_x >= SCREEN_WIDTH;
return m_closePanel && closed;
}
void ui::SlideOutPanel::push_new_element(std::shared_ptr<ui::Element> newElement) { m_elements.push_back(newElement); }

View File

@ -4,8 +4,8 @@
namespace
{
sdl::SharedTexture s_dialogCorners = nullptr;
sdl::SharedTexture s_menuBoundingCorners = nullptr;
sdl::SharedTexture s_dialogCorners{};
sdl::SharedTexture s_menuBoundingCorners{};
} // namespace
void ui::render_dialog_box(SDL_Texture *target, int x, int y, int width, int height)
@ -19,8 +19,10 @@ void ui::render_dialog_box(SDL_Texture *target, int x, int y, int width, int hei
s_dialogCorners->render_part(target, x, y, 0, 0, 16, 16);
sdl::render_rect_fill(target, x + 16, y, width - 32, 16, colors::DIALOG_BOX);
s_dialogCorners->render_part(target, (x + width) - 16, y, 16, 0, 16, 16);
// Middle
sdl::render_rect_fill(NULL, x, y + 16, width, height - 32, colors::DIALOG_BOX);
// Bottom
s_dialogCorners->render_part(target, x, (y + height) - 16, 0, 16, 16, 16);
sdl::render_rect_fill(NULL, x + 16, (y + height) - 16, width - 32, 16, colors::DIALOG_BOX);