applist: Add option to sort apps inside folders/outside only and introduce new settings menu to access beta features

This commit is contained in:
Joel16 2022-06-06 09:46:51 -04:00
parent 2c3ddf9cfe
commit e84723abd5
11 changed files with 314 additions and 50 deletions

View File

@ -23,7 +23,7 @@ add_definitions(
-DIMGUI_DISABLE_WIN32_FUNCTIONS -DIMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION -DIMGUI_ENABLE_FREETYPE
)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -ffast-math -mtune=cortex-a9 -mfpu=neon -Wall -Wno-psabi -std=gnu++17")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -ffast-math -mtune=cortex-a9 -mfpu=neon -Wall -Wno-psabi -fno-rtti -std=gnu++17")
set(VITA_MKSFOEX_FLAGS "${VITA_MKSFOEX_FLAGS} -d PARENTAL_LEVEL=1")
@ -45,6 +45,7 @@ add_executable(${PROJECT_NAME}
libs/imgui/imgui_widgets.cpp
libs/imgui/misc/freetype/imgui_freetype.cpp
source/applist.cpp
source/config.cpp
source/fs.cpp
source/gui.cpp
source/imgui_impl_vitagl.cpp
@ -75,6 +76,7 @@ target_link_libraries(${PROJECT_NAME}
SceDisplay_stub
SceGxm_stub
SceKernelDmacMgr_stub
SceLibJson_stub
SceLibKernel_stub
ScePower_stub
SceShaccCg_stub

40
dbbrowser.cpp Normal file
View File

@ -0,0 +1,40 @@
#include <psp2/kernel/clib.h>
#include "dbbrowser.h"
#include "log.h"
#include "sqlite3.h"
#include "utils.h"
namespace DBBrowser {
int GetTables(std::vector<std::string> &tables) {
sqlite3 *db = nullptr;
int ret = sqlite3_open_v2(db_path, &db, SQLITE_OPEN_READWRITE, nullptr);
if (ret != SQLITE_OK) {
Log::Error("sqlite3_open_v2 failed to open %s\n", db_path);
return ret;
}
std::string query = std::string("SELECT tbl_name FROM sqlite_schema WHERE type = 'table';");
sqlite3_stmt *stmt = nullptr;
ret = sqlite3_prepare_v2(db, query.c_str(), -1, &stmt, nullptr);
while ((ret = sqlite3_step(stmt)) == SQLITE_ROW) {
char res[64];
sceClibSnprintf(res, 64, "%s", sqlite3_column_text(stmt, 0));
tables.push_back(res);
}
if (ret != SQLITE_DONE) {
sqlite3_finalize(stmt);
sqlite3_close(db);
return ret;
}
sqlite3_finalize(stmt);
sqlite3_close(db);
return 0;
}
}

11
dbbrowser.h Normal file
View File

@ -0,0 +1,11 @@
#ifndef _VITA_HB_SORTER_DB_BROWSER_H_
#define _VITA_HB_SORTER_DB_BROWSER_H_
#include <vector>
#include <string>
namespace DBBrowser {
int GetTables(std::vector<std::string> &tables);
}
#endif

View File

@ -40,8 +40,6 @@ struct AppEntries {
std::vector<AppInfoChild> child_apps;
};
extern int sort_mode;
namespace AppList {
int Get(AppEntries &entries);
int Save(std::vector<AppInfoIcon> &entries);

18
include/config.h Normal file
View File

@ -0,0 +1,18 @@
#ifndef _VITA_HB_SORTER_CONFIG_H_
#define _VITA_HB_SORTER_CONFIG_H_
typedef struct {
bool beta_features = false;
int sort_by = 0;
int sort_folders = 0;
int sort_mode = 0;
} config_t;
extern config_t cfg;
namespace Config {
int Save(config_t &config);
int Load(void);
}
#endif

View File

@ -11,6 +11,7 @@ namespace FS {
bool DirExists(const std::string &path);
int CreateFile(const std::string &path);
int MakeDir(const std::string &path);
int WriteFile(const std::string &path, const void *data, SceSize size);
int RemoveFile(const std::string &path);
int CopyFile(const std::string &src_path, const std::string &dest_path);
std::string GetFileExt(const std::string &filename);

View File

@ -4,6 +4,7 @@
#include <string>
#include "applist.h"
#include "config.h"
#include "fs.h"
#include "log.h"
#include "sqlite3.h"
@ -205,8 +206,8 @@ namespace AppList {
}
bool SortAppAsc(const AppInfoIcon &entryA, const AppInfoIcon &entryB) {
std::string entryAname = sort_mode == 0? entryA.title : entryA.titleId;
std::string entryBname = sort_mode == 0? entryB.title : entryB.titleId;
std::string entryAname = cfg.sort_by == 0? entryA.title : entryA.titleId;
std::string entryBname = cfg.sort_by == 0? entryB.title : entryB.titleId;
std::transform(entryAname.begin(), entryAname.end(), entryAname.begin(), [](unsigned char c){ return std::tolower(c); });
std::transform(entryBname.begin(), entryBname.end(), entryBname.begin(), [](unsigned char c){ return std::tolower(c); });
@ -218,8 +219,8 @@ namespace AppList {
}
bool SortAppDesc(const AppInfoIcon &entryA, const AppInfoIcon &entryB) {
std::string entryAname = sort_mode == 0? entryA.title : entryA.titleId;
std::string entryBname = sort_mode == 0? entryB.title : entryB.titleId;
std::string entryAname = cfg.sort_by == 0? entryA.title : entryA.titleId;
std::string entryBname = cfg.sort_by == 0? entryB.title : entryB.titleId;
std::transform(entryAname.begin(), entryAname.end(), entryAname.begin(), [](unsigned char c){ return std::tolower(c); });
std::transform(entryBname.begin(), entryBname.end(), entryBname.begin(), [](unsigned char c){ return std::tolower(c); });
@ -231,8 +232,8 @@ namespace AppList {
}
bool SortChildAppAsc(const AppInfoChild &entryA, const AppInfoChild &entryB) {
std::string entryAname = sort_mode == 0? entryA.title : entryA.titleId;
std::string entryBname = sort_mode == 0? entryB.title : entryB.titleId;
std::string entryAname = cfg.sort_by == 0? entryA.title : entryA.titleId;
std::string entryBname = cfg.sort_by == 0? entryB.title : entryB.titleId;
std::transform(entryAname.begin(), entryAname.end(), entryAname.begin(), [](unsigned char c){ return std::tolower(c); });
std::transform(entryBname.begin(), entryBname.end(), entryBname.begin(), [](unsigned char c){ return std::tolower(c); });
@ -244,8 +245,8 @@ namespace AppList {
}
bool SortChildAppDesc(const AppInfoChild &entryA, const AppInfoChild &entryB) {
std::string entryAname = sort_mode == 0? entryA.title : entryA.titleId;
std::string entryBname = sort_mode == 0? entryB.title : entryB.titleId;
std::string entryAname = cfg.sort_by == 0? entryA.title : entryA.titleId;
std::string entryBname = cfg.sort_by == 0? entryB.title : entryB.titleId;
std::transform(entryAname.begin(), entryAname.end(), entryAname.begin(), [](unsigned char c){ return std::tolower(c); });
std::transform(entryBname.begin(), entryBname.end(), entryBname.begin(), [](unsigned char c){ return std::tolower(c); });
@ -267,7 +268,7 @@ namespace AppList {
}
// App/Game belongs to a folder
if (entries.icons[i].pageNo < 0) {
if ((entries.icons[i].pageNo < 0) && (cfg.sort_folders == 0 || cfg.sort_folders == 2)) {
for (unsigned int j = 0; j < entries.folders.size(); j++) {
if (entries.icons[i].pageId == entries.folders[j].pageId) {
entries.icons[i].pos = entries.folders[j].index;
@ -275,7 +276,7 @@ namespace AppList {
}
}
}
else {
else if ((entries.icons[i].pageNo >= 0) && (cfg.sort_folders == 0 || cfg.sort_folders == 1)) {
entries.icons[i].pos = pos;
entries.icons[i].pageId = entries.pages[pageCounter].pageId;
pos++;

117
source/config.cpp Normal file
View File

@ -0,0 +1,117 @@
#include <memory>
#include <psp2/json.h>
#include <psp2/io/fcntl.h>
#include <psp2/io/stat.h>
#include <psp2/kernel/clib.h>
#include "config.h"
#include "fs.h"
#include "log.h"
#include "utils.h"
#define CONFIG_VERSION 1
config_t cfg;
namespace Config {
static constexpr char config_path[] = "ux0:data/VITAHomebrewSorter/config.json";
static const char *config_file = "{\n\t\"beta_features\": %s,\n\t\"sort_by\": %d,\n\t\"sort_folders\": %d,\n\t\"sort_mode\": %d,\n\t\"version\": %d\n}";
static int config_version_holder = 0;
class Allocator : public sce::Json::MemAllocator {
public:
Allocator() {
}
virtual void *allocateMemory(size_t size, void *unk) override {
return std::malloc(size);
}
virtual void freeMemory(void *ptr, void *unk) override {
std::free(ptr);
}
};
int Save(config_t &config) {
int ret = 0;
std::unique_ptr<char[]> buffer(new char[128]);
SceSize len = sceClibSnprintf(buffer.get(), 128, config_file, config.beta_features? "true" : "false",
config.sort_by, config.sort_folders, config.sort_mode, CONFIG_VERSION);
if (R_FAILED(ret = FS::WriteFile(config_path, buffer.get(), len)))
return ret;
return 0;
}
int Load(void) {
int ret = 0;
if (!FS::FileExists(config_path)) {
cfg = {0};
return Config::Save(cfg);
}
SceUID file = 0;
if (R_FAILED(ret = file = sceIoOpen(config_path, SCE_O_RDONLY, 0))) {
Log::Error("sceIoOpen(%s) failed: 0x%lx\n", config_path, ret);
return ret;
}
SceSize size = sceIoLseek(file, 0, SCE_SEEK_END);
std::unique_ptr<char[]> buffer(new char[size]);
if (R_FAILED(ret = sceIoPread(file, buffer.get(), size, SCE_SEEK_SET))) {
Log::Error("sceIoRead(%s) failed: 0x%lx\n", config_path, ret);
sceIoClose(file);
return ret;
}
if (R_FAILED(ret = sceIoClose(file))) {
Log::Error("sceIoClose(%s) failed: 0x%lx\n", config_path, ret);
return ret;
}
Allocator *alloc = new Allocator();
sce::Json::InitParameter params;
params.allocator = alloc;
params.bufSize = size;
sce::Json::Initializer init = sce::Json::Initializer();
if (R_FAILED(ret = init.initialize(&params))) {
Log::Error("sce::Json::Initializer::initialize failed 0x%lx\n", ret);
init.terminate();
delete alloc;
return ret;
}
sce::Json::Value value = sce::Json::Value();
if (R_FAILED(ret = sce::Json::Parser::parse(value, buffer.get(), params.bufSize))) {
Log::Error("sce::Json::Parser::parse failed 0x%lx\n", ret);
init.terminate();
delete alloc;
return ret;
}
// We know sceJson API loops through the child values in root alphabetically.
cfg.beta_features = value.getValue(0).getBoolean();
cfg.sort_by = value.getValue(1).getInteger();
cfg.sort_folders = value.getValue(2).getInteger();
cfg.sort_mode = value.getValue(3).getInteger();
config_version_holder = value.getValue(4).getInteger();
init.terminate();
delete alloc;
// Delete config file if config file is updated. This will rarely happen.
if (config_version_holder < CONFIG_VERSION) {
sceIoRemove(config_path);
cfg = {0};
return Config::Save(cfg);
}
return 0;
}
}

View File

@ -105,7 +105,7 @@ namespace FS {
return bytes_read;
}
static int WriteFile(const std::string &path, const void *data, SceSize size) {
int WriteFile(const std::string &path, const void *data, SceSize size) {
int ret = 0, bytes_written = 0;
SceUID file = 0;
@ -141,23 +141,34 @@ namespace FS {
int CopyFile(const std::string &src_path, const std::string &dest_path) {
int ret = 0;
std::unique_ptr<unsigned char[]> buffer(new unsigned char[512]);
unsigned char *data = nullptr;
SceOff size = 0;
if (R_FAILED(ret = FS::GetFileSize(src_path, size)))
return ret;
data = new unsigned char[size];
if (!data)
return -1;
if (R_FAILED(ret = FS::ReadFile(src_path, buffer.get(), size)))
if (R_FAILED(ret = FS::ReadFile(src_path, data, size))) {
delete[] data;
return ret;
}
if (FS::FileExists(dest_path)) {
if (R_FAILED(ret = FS::RemoveFile(dest_path)))
if (R_FAILED(ret = FS::RemoveFile(dest_path))) {
delete[] data;
return ret;
}
}
if (R_FAILED(ret = FS::WriteFile(dest_path, buffer.get(), size)))
if (R_FAILED(ret = FS::WriteFile(dest_path, data, size))) {
delete[] data;
return ret;
}
delete[] data;
return 0;
}

View File

@ -5,6 +5,7 @@
#include <vitaGL.h>
#include "applist.h"
#include "config.h"
#include "fs.h"
#include "imgui_impl_vitagl.h"
#define IMGUI_DEFINE_MATH_OPERATORS
@ -14,8 +15,6 @@
#include "textures.h"
#include "utils.h"
int sort_mode = 0;
namespace Renderer {
static void End(bool clear, ImVec4 clear_color) {
glViewport(0, 0, static_cast<int>(ImGui::GetIO().DisplaySize.x), static_cast<int>(ImGui::GetIO().DisplaySize.y));
@ -44,6 +43,17 @@ namespace GUI {
};
enum SortBy {
SortTitle,
SortTitleID
};
enum SortFolders {
SortBoth,
SortAppsOnly,
SortFoldersOnly
};
enum SortMode {
SortDefault,
SortAsc,
SortDesc
@ -58,6 +68,8 @@ namespace GUI {
static bool backupExists = false;
static const ImVec2 tex_size = ImVec2(20, 20);
static const char *sort_by[] = {"Title", "Title ID"};
static const char *sort_folders[] = {"Both", "Apps only", "Folders only"};
static void SetupPopup(const char *id) {
ImGui::OpenPopup(id);
@ -220,30 +232,66 @@ namespace GUI {
GUI::ExitPopup();
}
static void SortTab(AppEntries &entries, SortBy &sort, State &state) {
static void SortTab(AppEntries &entries, State &state) {
ImGuiTableFlags tableFlags = ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersInner | ImGuiTableFlags_BordersOuter |
ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_ScrollY;
if (ImGui::BeginTabItem("Sort/Backup")) {
ImGui::Dummy(ImVec2(0.0f, 5.0f)); // Spacing
ImGui::PushID("sort_by");
ImGui::PushItemWidth(100.f);
if (ImGui::BeginCombo("", sort_by[cfg.sort_by])) {
for (int i = 0; i < IM_ARRAYSIZE(sort_by); i++) {
const bool is_selected = (cfg.sort_by == i);
if (ImGui::Selectable(sort_by[i], is_selected)) {
cfg.sort_by = i;
Config::Save(cfg);
}
if (is_selected)
ImGui::SetItemDefaultFocus();
}
ImGui::EndCombo();
}
ImGui::PopItemWidth();
ImGui::PopID();
ImGui::SameLine();
ImGui::SetNextItemWidth(100.0f);
if (ImGui::Combo("Sort by", &sort_mode, "Title\0Title ID\0")) {
sort = SortDefault;
ImGui::PushID("sort_folders");
ImGui::PushItemWidth(150.f);
if (ImGui::BeginCombo("", sort_folders[cfg.sort_folders])) {
for (int i = 0; i < IM_ARRAYSIZE(sort_folders); i++) {
const bool is_selected = (cfg.sort_folders == i);
if (ImGui::Selectable(sort_folders[i], is_selected)) {
cfg.sort_folders = i;
Config::Save(cfg);
}
if (is_selected)
ImGui::SetItemDefaultFocus();
}
ImGui::EndCombo();
}
ImGui::PopItemWidth();
ImGui::PopID();
ImGui::SameLine();
if (ImGui::RadioButton("Default", cfg.sort_mode == SortDefault)) {
cfg.sort_mode = SortDefault;
AppList::Get(entries);
}
ImGui::SameLine();
if (ImGui::RadioButton("Default", sort == SortDefault)) {
sort = SortDefault;
AppList::Get(entries);
}
ImGui::SameLine();
if (ImGui::RadioButton("Asc", sort == SortAsc)) {
sort = SortAsc;
if (ImGui::RadioButton("Asc", cfg.sort_mode == SortAsc)) {
cfg.sort_mode = SortAsc;
AppList::Get(entries);
std::sort(entries.icons.begin(), entries.icons.end(), AppList::SortAppAsc);
std::sort(entries.child_apps.begin(), entries.child_apps.end(), AppList::SortChildAppAsc);
@ -252,8 +300,8 @@ namespace GUI {
ImGui::SameLine();
if (ImGui::RadioButton("Desc", sort == SortDesc)) {
sort = SortDesc;
if (ImGui::RadioButton("Desc", cfg.sort_mode == SortDesc)) {
cfg.sort_mode = SortDesc;
AppList::Get(entries);
std::sort(entries.icons.begin(), entries.icons.end(), AppList::SortAppDesc);
std::sort(entries.child_apps.begin(), entries.child_apps.end(), AppList::SortChildAppDesc);
@ -262,10 +310,10 @@ namespace GUI {
ImGui::SameLine();
GUI::DisableButtonInit(sort == SortDefault);
GUI::DisableButtonInit(cfg.sort_mode == SortDefault);
if (ImGui::Button("Apply Sort"))
state = StateConfirm;
GUI::DisableButtonExit(sort == SortDefault);
GUI::DisableButtonExit(cfg.sort_mode == SortDefault);
if (backupExists) {
ImGui::SameLine();
@ -422,9 +470,28 @@ namespace GUI {
}
}
static void AboutTab(void) {
if (ImGui::BeginTabItem("About")) {
static void SettingsTab(void) {
if (ImGui::BeginTabItem("Settings")) {
ImGui::Dummy(ImVec2(0.0f, 5.0f)); // Spacing
ImGui::Indent(5.f);
ImGui::TextColored(ImVec4(0.70f, 0.16f, 0.31f, 1.0f), "Beta features:");
ImGui::Indent(15.f);
if (ImGui::RadioButton("Enabled", cfg.beta_features == true)) {
cfg.beta_features = true;
Config::Save(cfg);
}
ImGui::SameLine();
if (ImGui::RadioButton("Disabled", cfg.beta_features == false)) {
cfg.beta_features = false;
Config::Save(cfg);
}
ImGui::Dummy(ImVec2(0.0f, 10.0f)); // Spacing
ImGui::Unindent();
ImGui::Indent(5.f);
ImGui::TextColored(ImVec4(0.70f, 0.16f, 0.31f, 1.0f), "App Info:");
@ -465,10 +532,7 @@ namespace GUI {
FS::GetDirList("ux0:data/VITAHomebrewSorter/loadouts", loadouts);
int date_format = Utils::GetDateFormat();
std::string loadout_name;
SortBy sort = SortDefault;
State state = StateNone;
SceCtrlData pad = { 0 };
while (!done) {
@ -478,11 +542,11 @@ namespace GUI {
if (ImGui::Begin("VITA Homebrew Sorter", nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse)) {
if (ImGui::BeginTabBar("VITA Homebrew Sorter tabs")) {
GUI::SortTab(entries, sort, state);
GUI::DisableButtonInit(true);
GUI::SortTab(entries, state);
GUI::DisableButtonInit(cfg.beta_features? false : true);
GUI::LoadoutsTab(loadouts, state, date_format, loadout_name);
GUI::DisableButtonExit(true);
GUI::AboutTab();
GUI::DisableButtonExit(cfg.beta_features? false : true);
GUI::SettingsTab();
ImGui::EndTabBar();
}
}

View File

@ -1,6 +1,7 @@
#include <psp2/sysmodule.h>
#include <vitaGL.h>
#include "config.h"
#include "fs.h"
#include "gui.h"
#include "imgui_impl_vitagl.h"
@ -85,7 +86,7 @@ namespace Services {
SCE_CTRL_ENTER = Utils::GetEnterButton();
SCE_CTRL_CANCEL = Utils::GetCancelButton();
sceSysmoduleLoadModule(SCE_SYSMODULE_SQLITE);
sceSysmoduleLoadModule(SCE_SYSMODULE_JSON);
if (!FS::DirExists("ux0:data/VITAHomebrewSorter/backup"))
FS::MakeDir("ux0:data/VITAHomebrewSorter/backup");
@ -95,15 +96,15 @@ namespace Services {
Log::Init();
Textures::Init();
Power::InitThread();
Config::Load();
}
void Exit(void) {
// Clean up
Textures::Exit();
Log::Exit();
sceSysmoduleUnloadModule(SCE_SYSMODULE_SQLITE);
sceSysmoduleUnloadModule(SCE_SYSMODULE_JSON);
Utils::EndAppUtil();
ImGui_ImplVitaGL_Shutdown();
ImGui::DestroyContext();