JKSV/source/appstates/SettingsState.cpp
2025-10-09 20:22:07 -04:00

310 lines
12 KiB
C++

#include "appstates/SettingsState.hpp"
#include "appstates/BlacklistEditState.hpp"
#include "appstates/MainMenuState.hpp"
#include "appstates/MessageState.hpp"
#include "config/config.hpp"
#include "data/data.hpp"
#include "error.hpp"
#include "fs/fs.hpp"
#include "fslib.hpp"
#include "graphics/colors.hpp"
#include "input.hpp"
#include "keyboard.hpp"
#include "logging/logger.hpp"
#include "strings/strings.hpp"
#include "stringutil.hpp"
#include <array>
namespace
{
// All of these states share the same render target.
constexpr std::string_view SECONDARY_TARGET = "SecondaryTarget";
constexpr std::string_view CONFIG_KEY_NULL = "NULL";
enum
{
CHANGE_WORK_DIR = 0,
EDIT_BLACKLIST = 1,
CYCLE_ZIP = 15,
CYCLE_SORT_TYPE = 16,
TOGGLE_JKSM = 17,
TOGGLE_TRASH = 23,
CYCLE_SCALING = 24
};
// This is needed to be able to get and set keys by index. Anything "NULL" isn't a key that can be easily toggled.
constexpr std::array<std::string_view, 25> CONFIG_KEY_ARRAY = {CONFIG_KEY_NULL,
CONFIG_KEY_NULL,
config::keys::INCLUDE_DEVICE_SAVES,
config::keys::AUTO_BACKUP_ON_RESTORE,
config::keys::AUTO_NAME_BACKUPS,
config::keys::AUTO_UPLOAD,
config::keys::KEEP_LOCAL_BACKUPS,
config::keys::USE_TITLE_IDS,
config::keys::HOLD_FOR_DELETION,
config::keys::HOLD_FOR_RESTORATION,
config::keys::HOLD_FOR_OVERWRITE,
config::keys::ONLY_LIST_MOUNTABLE,
config::keys::LIST_ACCOUNT_SYS_SAVES,
config::keys::ALLOW_WRITING_TO_SYSTEM,
config::keys::EXPORT_TO_ZIP,
config::keys::ZIP_COMPRESSION_LEVEL,
config::keys::TITLE_SORT_TYPE,
config::keys::JKSM_TEXT_MODE,
config::keys::FORCE_ENGLISH,
config::keys::SHOW_DEVICE_USER,
config::keys::SHOW_BCAT_USER,
config::keys::SHOW_CACHE_USER,
config::keys::SHOW_SYSTEM_USER,
config::keys::ENABLE_TRASH_BIN,
CONFIG_KEY_NULL};
} // namespace
SettingsState::SettingsState()
: m_settingsMenu(ui::Menu::create(32, 10, 1000, 23, 555))
, m_controlGuide(ui::ControlGuide::create(strings::get_by_name(strings::names::CONTROL_GUIDES, 3)))
, m_renderTarget(sdl::TextureManager::load(SECONDARY_TARGET, 1080, 555, SDL_TEXTUREACCESS_TARGET))
{
SettingsState::load_settings_menu();
SettingsState::load_extra_strings();
SettingsState::update_menu_options();
}
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 xPressed = input::button_pressed(HidNpadButton_X);
const bool minusPressed = input::button_pressed(HidNpadButton_Minus);
m_settingsMenu->update(hasFocus);
if (aPressed) { SettingsState::toggle_options(); }
else if (xPressed) { SettingsState::reset_settings(); }
else if (minusPressed) { SettingsState::create_push_description_message(); }
else if (bPressed) { BaseState::deactivate(); }
m_controlGuide->update(hasFocus);
}
void SettingsState::sub_update() { m_controlGuide->sub_update(); }
void SettingsState::render()
{
const bool hasFocus = BaseState::has_focus();
m_renderTarget->clear(colors::TRANSPARENT);
m_settingsMenu->render(m_renderTarget, hasFocus);
m_renderTarget->render(sdl::Texture::Null, 201, 91);
m_controlGuide->render(sdl::Texture::Null, hasFocus);
}
void SettingsState::load_settings_menu()
{
for (int i = 0; const char *option = strings::get_by_name(strings::names::SETTINGS_MENU, i); i++)
{
m_settingsMenu->add_option(option);
}
}
void SettingsState::load_extra_strings()
{
for (int i = 0; const char *onOff = strings::get_by_name(strings::names::ON_OFF, i); i++) { m_onOff[i] = onOff; }
for (int i = 0; const char *sortType = strings::get_by_name(strings::names::SORT_TYPES, i); i++)
{
m_sortTypes[i] = sortType;
}
}
void SettingsState::update_menu_options()
{
static constexpr std::array<int, 20> TOGGLE_INDEXES = {2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
12, 13, 14, 17, 18, 19, 20, 21, 22, 23};
for (const int index : TOGGLE_INDEXES)
{
const char *optionFormat = strings::get_by_name(strings::names::SETTINGS_MENU, index);
const uint8_t value = config::get_by_key(CONFIG_KEY_ARRAY[index]);
const char *status = SettingsState::get_status_text(value);
const std::string option = stringutil::get_formatted_string(optionFormat, status);
m_settingsMenu->edit_option(index, option);
}
{
const char *zipCompFormat = strings::get_by_name(strings::names::SETTINGS_MENU, CYCLE_ZIP);
const uint8_t zipLevel = config::get_by_key(CONFIG_KEY_ARRAY[CYCLE_ZIP]);
const std::string zipOption = stringutil::get_formatted_string(zipCompFormat, zipLevel);
m_settingsMenu->edit_option(CYCLE_ZIP, zipOption);
}
{
const char *titleSortFormat = strings::get_by_name(strings::names::SETTINGS_MENU, CYCLE_SORT_TYPE);
const uint8_t sortType = config::get_by_key(CONFIG_KEY_ARRAY[CYCLE_SORT_TYPE]);
const char *typeText = SettingsState::get_sort_type_text(sortType);
const std::string sortTypeOption = stringutil::get_formatted_string(titleSortFormat, typeText);
m_settingsMenu->edit_option(CYCLE_SORT_TYPE, sortTypeOption);
}
{
const char *scalingFormat = strings::get_by_name(strings::names::SETTINGS_MENU, CYCLE_SCALING);
const double scaling = config::get_animation_scaling();
const std::string scalingOption = stringutil::get_formatted_string(scalingFormat, scaling);
m_settingsMenu->edit_option(CYCLE_SCALING, scalingOption);
}
}
void SettingsState::change_working_directory()
{
const int popTicks = ui::PopMessageManager::DEFAULT_TICKS;
const char *inputHeader = strings::get_by_name(strings::names::KEYBOARD, 2);
const char *popSuccessFormat = strings::get_by_name(strings::names::SETTINGS_POPS, 1);
const char *popFailed = strings::get_by_name(strings::names::SETTINGS_POPS, 2);
const fslib::Path oldPath = config::get_working_directory();
std::array<char, FS_MAX_PATH> pathBuffer = {0};
const bool input =
keyboard::get_input(SwkbdType_Normal, oldPath.string().c_str(), inputHeader, pathBuffer.data(), FS_MAX_PATH);
if (!input) { return; }
const fslib::Path newPath{pathBuffer.data()};
if (!newPath.is_valid())
{
ui::PopMessageManager::push_message(popTicks, popFailed);
return;
}
const bool exists = fslib::directory_exists(newPath);
bool moved{};
if (exists)
{
moved = fs::move_directory_recursively(oldPath, newPath);
error::fslib(fslib::delete_directory_recursively(oldPath));
}
else { moved = fslib::rename_directory(oldPath, newPath); }
if (!moved)
{
ui::PopMessageManager::push_message(popTicks, popFailed);
return;
}
// Since the path was validated before, this shouldn't need to be checked.
config::set_working_directory(newPath);
const std::string newPathString = newPath.string();
std::string popMessage = stringutil::get_formatted_string(popSuccessFormat, newPathString.c_str());
ui::PopMessageManager::push_message(popTicks, popMessage);
}
void SettingsState::create_push_blacklist_edit()
{
const bool isEmpty = config::blacklist_is_empty();
if (isEmpty)
{
const int popTicks = ui::PopMessageManager::DEFAULT_TICKS;
const char *popEmpty = strings::get_by_name(strings::names::SETTINGS_POPS, 0);
ui::PopMessageManager::push_message(popTicks, popEmpty);
return;
}
BlacklistEditState::create_and_push();
}
void SettingsState::toggle_options()
{
const int selected = m_settingsMenu->get_selected();
switch (selected)
{
case CHANGE_WORK_DIR: SettingsState::change_working_directory(); break;
case EDIT_BLACKLIST: SettingsState::create_push_blacklist_edit(); break;
case CYCLE_ZIP: SettingsState::cycle_zip_level(); break;
case CYCLE_SORT_TYPE: SettingsState::cycle_sort_type(); break;
case TOGGLE_JKSM: SettingsState::toggle_jksm_mode(); break;
case TOGGLE_TRASH: SettingsState::toggle_trash_folder(); break;
case CYCLE_SCALING: SettingsState::cycle_anim_scaling(); break;
default: config::toggle_by_key(CONFIG_KEY_ARRAY[selected]); break;
}
SettingsState::update_menu_options();
}
void SettingsState::reset_settings()
{
config::reset_to_default();
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_push_fade(description);
}
void SettingsState::cycle_zip_level()
{
uint8_t zipLevel = config::get_by_key(config::keys::ZIP_COMPRESSION_LEVEL);
if (++zipLevel % 10 == 0) { zipLevel = 0; }
config::set_by_key(config::keys::ZIP_COMPRESSION_LEVEL, zipLevel);
}
void SettingsState::cycle_sort_type()
{
uint8_t sortType = config::get_by_key(config::keys::TITLE_SORT_TYPE);
if (++sortType % 3 == 0) { sortType = 0; }
config::set_by_key(config::keys::TITLE_SORT_TYPE, sortType);
data::UserList users{};
data::get_users(users);
for (data::User *user : users) { user->sort_data(); }
MainMenuState::refresh_view_states();
}
void SettingsState::toggle_jksm_mode()
{
config::toggle_by_key(config::keys::JKSM_TEXT_MODE);
MainMenuState::initialize_view_states();
}
void SettingsState::toggle_trash_folder()
{
const bool trashEnabled = config::get_by_key(config::keys::ENABLE_TRASH_BIN);
const fslib::Path trashPath{config::get_working_directory() / "_TRASH_"};
config::toggle_by_key(config::keys::ENABLE_TRASH_BIN);
if (trashEnabled) { error::fslib(fslib::delete_directory_recursively(trashPath)); }
else { error::fslib(fslib::create_directory(trashPath)); }
}
void SettingsState::cycle_anim_scaling()
{
double scaling = config::get_animation_scaling();
if ((scaling += 0.25f) > 4.0f) { scaling = 1.0f; }
config::set_animation_scaling(scaling);
ui::Transition::update_scaling();
}
const char *SettingsState::get_status_text(uint8_t value)
{
if (value > 1) { return nullptr; }
return m_onOff[value];
}
const char *SettingsState::get_sort_type_text(uint8_t value)
{
if (value > 2) { return nullptr; }
return m_sortTypes[value];
}