Add MessageState, tweak PopMessage again, add git templates.

This commit is contained in:
J-D-K
2025-08-07 17:41:58 -04:00
parent 5f4badf716
commit 7d9a294745
36 changed files with 1694 additions and 1507 deletions

View File

@@ -27,9 +27,9 @@
namespace
{
/// @brief Build month.
constexpr uint8_t BUILD_MON = 7;
constexpr uint8_t BUILD_MON = 8;
/// @brief Build day.
constexpr uint8_t BUILD_DAY = 28;
constexpr uint8_t BUILD_DAY = 7;
/// @brief Year.
constexpr uint16_t BUILD_YEAR = 2025;
} // namespace
@@ -195,7 +195,7 @@ bool JKSV::create_directories()
void JKSV::add_color_chars()
{
sdl::text::add_color_character(L'#', colors::BLUE);
sdl::text::add_color_character(L'*', colors::RED);
sdl::text::add_color_character(L'*', colors::DARK_RED);
sdl::text::add_color_character(L'<', colors::YELLOW);
sdl::text::add_color_character(L'>', colors::GREEN);
sdl::text::add_color_character(L'`', colors::BLUE_GREEN);

View File

@@ -285,14 +285,14 @@ void BackupMenuState::confirm_overwrite()
m_dataStruct->remoteItem = m_remoteListing.at(entry.index);
const char *itemName = m_dataStruct->remoteItem->get_name().data();
const std::string query = stringutil::get_formatted_string(confirmTemplate, itemName);
ProgressConfirm::create_and_push(query, holdRequired, tasks::backup::overwrite_backup_remote, m_dataStruct);
ProgressConfirm::create_push_fade(query, holdRequired, tasks::backup::overwrite_backup_remote, m_dataStruct);
}
else if (entry.type == MenuEntryType::Local)
{
m_dataStruct->path = m_directoryPath / m_directoryListing[entry.index];
const char *targetName = m_directoryListing[entry.index];
const std::string query = stringutil::get_formatted_string(confirmTemplate, targetName);
ProgressConfirm::create_and_push(query, holdRequired, tasks::backup::overwrite_backup_local, m_dataStruct);
ProgressConfirm::create_push_fade(query, holdRequired, tasks::backup::overwrite_backup_local, m_dataStruct);
}
}
@@ -330,8 +330,7 @@ void BackupMenuState::confirm_restore()
m_dataStruct->path = target;
const std::string query = stringutil::get_formatted_string(confirmTemplate, m_directoryListing[entry.index]);
auto confirm = ProgressConfirm::create(query, holdRequired, tasks::backup::restore_backup_local, m_dataStruct);
FadeState::create_and_push(colors::DIM_BACKGROUND, 0x00, 0x88, confirm);
ProgressConfirm::create_push_fade(query, holdRequired, tasks::backup::restore_backup_local, m_dataStruct);
}
else if (entry.type == MenuEntryType::Remote)
{
@@ -339,8 +338,7 @@ void BackupMenuState::confirm_restore()
const std::string query = stringutil::get_formatted_string(confirmTemplate, target->get_name().data());
m_dataStruct->remoteItem = target;
auto confirm = ProgressConfirm::create(query, holdRequired, tasks::backup::restore_backup_remote, m_dataStruct);
FadeState::create_and_push(colors::DIM_BACKGROUND, 0x00, 0x88, confirm);
ProgressConfirm::create_push_fade(query, holdRequired, tasks::backup::restore_backup_remote, m_dataStruct);
}
}

View File

@@ -0,0 +1,65 @@
#include "appstates/MessageState.hpp"
#include "StateManager.hpp"
#include "appstates/FadeState.hpp"
#include "colors.hpp"
#include "input.hpp"
#include "strings.hpp"
MessageState::MessageState(std::string_view message)
: m_message(message)
{
MessageState::initialize_static_members();
}
MessageState::~MessageState()
{
FadeState::create_and_push(colors::DIM_BACKGROUND, colors::ALPHA_FADE_END, colors::ALPHA_FADE_BEGIN, nullptr);
}
std::shared_ptr<MessageState> MessageState::create(std::string_view message) { return std::make_shared<MessageState>(message); }
std::shared_ptr<MessageState> MessageState::create_and_push(std::string_view message)
{
auto newState = MessageState::create(message);
StateManager::push_state(newState);
return newState;
}
std::shared_ptr<MessageState> MessageState::create_and_push_fade(std::string_view message)
{
auto newState = MessageState::create(message);
FadeState::create_and_push(colors::DIM_BACKGROUND, 0x00, 0x88, newState);
return newState;
}
void MessageState::update()
{
// To do: I only use this in one place right now. I'm not sure this guards correctly here?
const bool aPressed = input::button_pressed(HidNpadButton_A);
m_triggerGuard = m_triggerGuard || (aPressed && !m_triggerGuard);
const bool finished = m_triggerGuard && aPressed;
if (finished) { BaseState::deactivate(); }
}
void MessageState::render()
{
const bool hasFocus = BaseState::has_focus();
sdl::render_rect_fill(sdl::Texture::Null, 0, 0, 1280, 720, colors::DIM_BACKGROUND);
sm_dialog->render(sdl::Texture::Null, hasFocus);
sdl::text::render(sdl::Texture::Null, 312, 288, 20, 656, colors::WHITE, m_message);
sdl::render_line(sdl::Texture::Null, 280, 454, 999, 454, colors::DIV_COLOR);
sdl::text::render(sdl::Texture::Null, sm_okX, 476, 22, sdl::text::NO_WRAP, colors::WHITE, sm_okText);
}
void MessageState::initialize_static_members()
{
static constexpr int HALF_WIDTH = 640;
if (sm_okText && sm_dialog) { return; }
sm_okText = strings::get_by_name(strings::names::YES_NO_OK, 2);
sm_okX = HALF_WIDTH - (sdl::text::get_width(22, sm_okText) / 2);
sm_dialog = ui::DialogBox::create(280, 262, 720, 256);
}

View File

@@ -2,6 +2,7 @@
#include "appstates/BlacklistEditState.hpp"
#include "appstates/MainMenuState.hpp"
#include "appstates/MessageState.hpp"
#include "colors.hpp"
#include "config.hpp"
#include "data/data.hpp"
@@ -69,12 +70,14 @@ std::shared_ptr<SettingsState> SettingsState::create() { return std::make_shared
void SettingsState::update()
{
const bool hasFocus = BaseState::has_focus();
const bool aPressed = input::button_pressed(HidNpadButton_A);
const bool bPressed = input::button_pressed(HidNpadButton_B);
const bool hasFocus = BaseState::has_focus();
const bool aPressed = input::button_pressed(HidNpadButton_A);
const bool bPressed = input::button_pressed(HidNpadButton_B);
const bool minusPressed = input::button_pressed(HidNpadButton_Minus);
m_settingsMenu.update(hasFocus);
if (aPressed) { SettingsState::toggle_options(); }
else if (minusPressed) { SettingsState::create_push_description_message(); }
else if (bPressed) { BaseState::deactivate(); }
}
@@ -176,6 +179,14 @@ void SettingsState::toggle_options()
SettingsState::update_menu_options();
}
void SettingsState::create_push_description_message()
{
const int selected = m_settingsMenu.get_selected();
const char *description = strings::get_by_name(strings::names::SETTINGS_DESCRIPTIONS, selected);
MessageState::create_and_push_fade(description);
}
void SettingsState::cycle_zip_level()
{
uint8_t zipLevel = config::get_by_key(config::keys::ZIP_COMPRESSION_LEVEL);

View File

@@ -10,8 +10,8 @@
#include <cstring>
data::TitleInfo::TitleInfo(uint64_t applicationID)
: m_applicationID{applicationID}
, m_data{std::make_unique<NsApplicationControlData>()}
: m_applicationID(applicationID)
, m_data(std::make_unique<NsApplicationControlData>())
{
static constexpr size_t SIZE_CTRL_DATA = sizeof(NsApplicationControlData);
static constexpr size_t SIZE_NACP = sizeof(NacpStruct);
@@ -27,7 +27,7 @@ data::TitleInfo::TitleInfo(uint64_t applicationID)
data,
SIZE_CTRL_DATA,
&controlSize));
const bool entryError = !isSystem && !getError && error::libnx(nacpGetLanguageEntry(&data->nacp, &entry));
const bool entryError = !getError && error::libnx(nacpGetLanguageEntry(&data->nacp, &entry));
if (isSystem || getError)
{
const std::string appIDHex = stringutil::get_formatted_string("%04X", m_applicationID & 0xFFFF);
@@ -50,8 +50,8 @@ data::TitleInfo::TitleInfo(uint64_t applicationID)
// To do: Make this safer...
data::TitleInfo::TitleInfo(uint64_t applicationID, NsApplicationControlData &controlData)
: m_applicationID{applicationID}
, m_data{std::make_unique<NsApplicationControlData>()}
: m_applicationID(applicationID)
, m_data(std::make_unique<NsApplicationControlData>())
{
NsApplicationControlData *data = m_data.get();

View File

@@ -3,14 +3,14 @@
#include "config.hpp"
#include "error.hpp"
#include "fs/fs.hpp"
#include "fslib.hpp"
#include "logger.hpp"
#include "strings.hpp"
#include "stringutil.hpp"
#include <algorithm>
#include <array>
#include <mutex>
#include <string_view>
#include <string>
#include <switch.h>
#include <thread>
#include <unordered_map>
@@ -18,28 +18,23 @@
namespace
{
/// @brief Struct used for reading the cache from file.
// clang-format off
typedef struct
{
uint64_t applicationID;
NsApplicationControlData data;
} CacheEntry;
// clang-format on
/// @brief This contains the user accounts on the system.
std::vector<data::User> s_users{};
// Map of Title info paired with its title/application
std::unordered_map<uint64_t, data::TitleInfo> s_titleinfo;
/// @brief Path used for cacheing title information since NS got slow on 20.0+
constexpr std::string_view PATH_CACHE_PATH = "sdmc:/config/JKSV/cache.bin";
constexpr std::string_view PATH_CACHE_PATH = "sdmc:/config/JKSV/cache.zip";
// These are the ID's used for system type users.
constexpr AccountUid ID_SYSTEM_USER = {FsSaveDataType_System};
constexpr AccountUid ID_BCAT_USER = {FsSaveDataType_Bcat};
constexpr AccountUid ID_DEVICE_USER = {FsSaveDataType_Device};
constexpr AccountUid ID_CACHE_USER = {FsSaveDataType_Cache};
/// @brief This is for loading the cache.
constexpr size_t SIZE_CTRL_DATA = sizeof(NsApplicationControlData);
} // namespace
// Declarations here. Definitions at bottom. These should appear in the order called.
@@ -182,59 +177,40 @@ static void import_svi_files()
static bool read_cache_file()
{
constexpr size_t SIZE_UNSIGNED = sizeof(unsigned int);
constexpr size_t SIZE_CACHE_ENTRY = sizeof(CacheEntry);
fs::MiniUnzip cacheZip{PATH_CACHE_PATH};
if (!cacheZip.is_open()) { return false; }
fslib::File cache{PATH_CACHE_PATH, FsOpenMode_Read};
if (error::fslib(cache.is_open())) { return false; }
NsApplicationControlData controlBuffer{};
do {
const bool read = cacheZip.read(&controlBuffer, SIZE_CTRL_DATA) == SIZE_CTRL_DATA;
if (!read) { continue; }
unsigned int titleCount{};
const bool countRead = cache.read(&titleCount, SIZE_UNSIGNED) == SIZE_UNSIGNED;
if (!countRead) { return false; }
const char *filename = cacheZip.get_filename();
const uint64_t applicationID = std::strtoull(filename, nullptr, 16);
data::TitleInfo newInfo{applicationID, controlBuffer};
auto entryBuffer = std::make_unique<CacheEntry[]>(titleCount); // I've read there might not be any point in error checking.
const int64_t cacheSize = SIZE_CACHE_ENTRY * titleCount;
const bool cacheRead = cache.read(entryBuffer.get(), cacheSize) == cacheSize;
if (!cacheRead) { return false; }
s_titleinfo.emplace(applicationID, std::move(newInfo));
} while (cacheZip.next_file());
// Loop through the cache entries and emplace them to the map.
for (unsigned int i = 0; i < titleCount; i++)
{
CacheEntry &entry = entryBuffer[i];
data::TitleInfo newInfo{entry.applicationID, entry.data};
s_titleinfo.emplace(entry.applicationID, std::move(newInfo));
}
return true;
}
static void create_cache_file()
{
static constexpr size_t SIZE_UNSIGNED = sizeof(unsigned int);
static constexpr size_t SIZE_UINT64 = sizeof(uint64_t);
static constexpr size_t SIZE_CTRL_DATA = sizeof(NsApplicationControlData);
fslib::File cache{PATH_CACHE_PATH, FsOpenMode_Create | FsOpenMode_Write};
if (error::fslib(cache.is_open())) { return; }
// This will make more sense later. I promise.
unsigned int titleCount{};
const bool countWrite = cache.write(&titleCount, SIZE_UNSIGNED) == SIZE_UNSIGNED;
if (!countWrite) { return; }
fs::MiniZip cacheZip{PATH_CACHE_PATH};
if (!cacheZip.is_open()) { return; }
for (auto &[applicationID, titleInfo] : s_titleinfo)
{
if (!titleInfo.has_control_data()) { continue; }
const NsApplicationControlData *data = titleInfo.get_control_data();
const bool idWrite = cache.write(&applicationID, SIZE_UINT64) == SIZE_UINT64;
const bool dataWrite = cache.write(data, SIZE_CTRL_DATA) == SIZE_CTRL_DATA;
if (!idWrite || !dataWrite) { return; }
const NsApplicationControlData *controlData = titleInfo.get_control_data();
const std::string cacheName = stringutil::get_formatted_string("%016llX", applicationID);
const bool opened = cacheZip.open_new_file(cacheName);
if (!opened) { continue; }
++titleCount;
const bool controlWritten = cacheZip.write(controlData, SIZE_CTRL_DATA);
if (!controlWritten) { logger::log("Error writing control data to zip!"); }
cacheZip.close_current_file();
}
// Go back to beginning and write the final count.
cache.seek(0, cache.BEGINNING);
cache.write(&titleCount, SIZE_UNSIGNED);
}

View File

@@ -16,33 +16,33 @@ namespace
constexpr std::string_view HEADER_AUTH_BEARER = "Authorization: Bearer ";
// Content type headers used.
const char *HEADER_CONTENT_TYPE_JSON = "Content-Type: application/json";
const char *HEADER_CONTENT_TYPE_FORM = "Content-Type: application/x-www-form-urlencoded";
constexpr const char *HEADER_CONTENT_TYPE_JSON = "Content-Type: application/json";
constexpr const char *HEADER_CONTENT_TYPE_FORM = "Content-Type: application/x-www-form-urlencoded";
// This is used for upload/patch for grabbing the upload location.
constexpr std::string_view HEADER_UPLOAD_LOCATION = "Location";
// These are API endpoints used in multiple request calls.
const char *URL_OAUTH2_TOKEN_URL = "https://oauth2.googleapis.com/token";
const char *URL_DRIVE_FILE_API = "https://www.googleapis.com/drive/v3/files";
const char *URL_DRIVE_UPLOAD_API = "https://www.googleapis.com/upload/drive/v3/files";
constexpr const char *URL_OAUTH2_TOKEN_URL = "https://oauth2.googleapis.com/token";
constexpr const char *URL_DRIVE_FILE_API = "https://www.googleapis.com/drive/v3/files";
constexpr const char *URL_DRIVE_UPLOAD_API = "https://www.googleapis.com/upload/drive/v3/files";
// These are json keys that are used for various requests.
const char *JSON_KEY_ACCESS_TOKEN = "access_token";
const char *JSON_KEY_CLIENT_ID = "client_id";
const char *JSON_KEY_CLIENT_SECRET = "client_secret";
const char *JSON_KEY_DEVICE_CODE = "device_code";
const char *JSON_KEY_EXPIRES_IN = "expires_in";
const char *JSON_KEY_GRANT_TYPE = "grant_type";
const char *JSON_KEY_ID = "id";
const char *JSON_KEY_INSTALLED = "installed";
const char *JSON_KEY_MIMETYPE = "mimeType";
const char *JSON_KEY_NAME = "name";
const char *JSON_KEY_PARENTS = "parents";
const char *JSON_KEY_REFRESH_TOKEN = "refresh_token";
constexpr const char *JSON_KEY_ACCESS_TOKEN = "access_token";
constexpr const char *JSON_KEY_CLIENT_ID = "client_id";
constexpr const char *JSON_KEY_CLIENT_SECRET = "client_secret";
constexpr const char *JSON_KEY_DEVICE_CODE = "device_code";
constexpr const char *JSON_KEY_EXPIRES_IN = "expires_in";
constexpr const char *JSON_KEY_GRANT_TYPE = "grant_type";
constexpr const char *JSON_KEY_ID = "id";
constexpr const char *JSON_KEY_INSTALLED = "installed";
constexpr const char *JSON_KEY_MIMETYPE = "mimeType";
constexpr const char *JSON_KEY_NAME = "name";
constexpr const char *JSON_KEY_PARENTS = "parents";
constexpr const char *JSON_KEY_REFRESH_TOKEN = "refresh_token";
/// @brief Folder mimetype string.
const char *MIME_TYPE_DIRECTORY = "application/vnd.google-apps.folder";
constexpr const char *MIME_TYPE_DIRECTORY = "application/vnd.google-apps.folder";
} // namespace
remote::GoogleDrive::GoogleDrive()

View File

@@ -17,7 +17,7 @@
namespace
{
/// @brief This is just the string for finding and creating the JKSV dir.
const char *STRING_JKSV_DIR = "JKSV";
constexpr const char *STRING_JKSV_DIR = "JKSV";
/// @brief This is the single (for now) instance of a storage class.
std::unique_ptr<remote::Storage> s_storage{};

View File

@@ -10,7 +10,7 @@ ui::PopMessage::PopMessage(int ticks, std::string_view message)
, m_message(message)
, m_y(PopMessage::START_Y)
, m_width(PopMessage::START_WIDTH)
, m_dialog(ui::DialogBox::create(PopMessage::PERMA_X,
, m_dialog(ui::DialogBox::create(PopMessage::START_X,
m_y - 6,
PopMessage::START_WIDTH,
PopMessage::PERMA_HEIGHT,
@@ -29,7 +29,7 @@ void ui::PopMessage::render()
if (!m_yMet) { return; }
// This avoids allocating and returning another std::string.
const std::string_view message(m_message.c_str(), m_substrOffset);
sdl::text::render(sdl::Texture::Null, PopMessage::PERMA_X + 16, m_y + 5, 22, sdl::text::NO_WRAP, colors::BLACK, message);
sdl::text::render(sdl::Texture::Null, m_textX, m_y + 5, 22, sdl::text::NO_WRAP, colors::BLACK, message);
}
bool ui::PopMessage::finished() const { return m_finished; }
@@ -54,6 +54,8 @@ void ui::PopMessage::update_y(double targetY)
void ui::PopMessage::update_text_offset()
{
static constexpr int HALF_WIDTH = 640;
const int messageLength = m_message.length();
if (!m_yMet || m_substrOffset >= messageLength || !m_typeTimer.is_triggered()) { return; }
@@ -66,7 +68,13 @@ void ui::PopMessage::update_text_offset()
m_substrOffset += unitCount;
const std::string_view subMessage(message, m_substrOffset);
const int dialogWidth = sdl::text::get_width(22, subMessage) + 32;
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_dialog->set_xy(dialogX, m_dialog->NO_SET);
m_dialog->set_width_height(dialogWidth, m_dialog->NO_SET);
if (m_substrOffset >= messageLength) { m_displayTimer.start(m_ticks); }
}

View File

@@ -56,7 +56,7 @@ void ui::TextScroll::initialize(std::string_view text,
m_clearColor = clearColor;
m_targetWidth = width;
m_targetHeight = height;
m_textY = (m_targetHeight / 2) - (m_fontSize / 2) - 1; // This gets the job done, but could be better.
m_textY = (m_targetHeight / 2) - (m_fontSize / 2);
m_scrollTimer.start(TICKS_SCROLL_TRIGGER);
{