mirror of
https://github.com/XorTroll/uLaunch
synced 2024-11-26 13:50:25 +00:00
Add folders, failed launch handling and more
This commit is contained in:
parent
5eaf42fe5e
commit
d1ef9de979
15 changed files with 329 additions and 118 deletions
|
@ -9,7 +9,8 @@ namespace am
|
|||
Invalid,
|
||||
StartupScreen,
|
||||
MenuNormal,
|
||||
MenuApplicationSuspended
|
||||
MenuApplicationSuspended,
|
||||
MenuLaunchFailure
|
||||
};
|
||||
|
||||
enum class QMenuMessage
|
||||
|
|
|
@ -12,6 +12,7 @@ namespace cfg
|
|||
|
||||
struct TitleRecord
|
||||
{
|
||||
std::string json_name; // Empty for non-SD, normal title records
|
||||
u32 title_type;
|
||||
std::string sub_folder; // Empty for root, name for a certain folder
|
||||
|
||||
|
@ -33,7 +34,9 @@ namespace cfg
|
|||
|
||||
ResultWith<TitleList> LoadTitleList(bool cache);
|
||||
|
||||
void SaveRecord(TitleRecord record);
|
||||
bool MoveTitleToDirectory(TitleList &list, u64 app_id, std::string dir);
|
||||
TitleFolder &FindFolderByName(TitleList &list, std::string name);
|
||||
|
||||
std::string GetTitleCacheIconPath(u64 app_id);
|
||||
std::string GetNROCacheIconPath(std::string path);
|
||||
|
|
|
@ -6,6 +6,12 @@
|
|||
namespace os
|
||||
{
|
||||
#define OS_MAX_TITLE_COUNT 64000
|
||||
#define OS_FLOG_APP_ID 0x01008BB00013C000
|
||||
|
||||
inline constexpr bool IsFlogTitle(u64 app_id)
|
||||
{
|
||||
return (app_id == OS_FLOG_APP_ID);
|
||||
}
|
||||
|
||||
ResultWith<std::vector<cfg::TitleRecord>> QueryInstalledTitles(bool cache);
|
||||
}
|
|
@ -24,8 +24,7 @@ using JSON = nlohmann::json;
|
|||
#define Q_DB_MOUNT_NAME "qsave"
|
||||
#define Q_DB_MOUNT_PATH Q_DB_MOUNT_NAME ":/"
|
||||
#define Q_BASE_DB_DIR Q_DB_MOUNT_PATH Q_BASE_DIR
|
||||
|
||||
#define Q_MENU_JSON Q_BASE_SD_DIR "/menu.json"
|
||||
#define Q_ENTRIES_PATH Q_BASE_SD_DIR "/entries"
|
||||
|
||||
// Thanks SciresM
|
||||
#define R_TRY(res_expr) \
|
||||
|
|
|
@ -8,15 +8,46 @@
|
|||
|
||||
namespace cfg
|
||||
{
|
||||
void SaveRecord(TitleRecord record)
|
||||
{
|
||||
JSON entry = JSON::object();
|
||||
entry["type"] = record.title_type;
|
||||
entry["folder"] = record.sub_folder;
|
||||
|
||||
// Prepare JSON path
|
||||
std::string basepath = Q_ENTRIES_PATH;
|
||||
std::string json = basepath;
|
||||
if((TitleType)record.title_type == TitleType::Homebrew)
|
||||
{
|
||||
json += "/" + std::to_string(fs::GetFileSize(record.nro_path)) + ".json";
|
||||
entry["nro_path"] = record.nro_path;
|
||||
}
|
||||
else if((TitleType)record.title_type == TitleType::Installed)
|
||||
{
|
||||
auto strappid = util::FormatApplicationId(record.app_id);
|
||||
json += "/" + strappid + ".json";
|
||||
entry["application_id"] = strappid;
|
||||
}
|
||||
if(!record.json_name.empty()) json = basepath + "/" + record.json_name;
|
||||
if(fs::ExistsFile(json)) fs::DeleteFile(json);
|
||||
|
||||
std::ofstream ofs(json);
|
||||
ofs << entry;
|
||||
ofs.close();
|
||||
}
|
||||
|
||||
bool MoveTitleToDirectory(TitleList &list, u64 app_id, std::string folder)
|
||||
{
|
||||
bool title_found = false;
|
||||
TitleRecord record_copy = {};
|
||||
std::string recjson;
|
||||
|
||||
// Search in root first
|
||||
auto find = STLITER_FINDWITHCONDITION(list.root.titles, tit, (tit.app_id == app_id));
|
||||
if(STLITER_ISFOUND(list.root.titles, find))
|
||||
{
|
||||
if(folder.empty()) return true; // It is already on root...?
|
||||
recjson = STLITER_UNWRAP(find).json_name;
|
||||
|
||||
list.root.titles.erase(find);
|
||||
title_found = true;
|
||||
|
@ -26,13 +57,15 @@ namespace cfg
|
|||
{
|
||||
for(auto &fld: list.folders)
|
||||
{
|
||||
auto find = STLITER_FINDWITHCONDITION(fld.titles, item, (item.app_id == app_id));
|
||||
auto find = STLITER_FINDWITHCONDITION(fld.titles, entry, (entry.app_id == app_id));
|
||||
if(STLITER_ISFOUND(fld.titles, find))
|
||||
{
|
||||
if(fld.name == folder) return true; // It is already on that folder...?
|
||||
recjson = STLITER_UNWRAP(find).json_name;
|
||||
|
||||
fld.titles.erase(find);
|
||||
title_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -42,6 +75,8 @@ namespace cfg
|
|||
TitleRecord title = {};
|
||||
title.app_id = app_id;
|
||||
title.title_type = (u32)TitleType::Installed;
|
||||
title.json_name = recjson;
|
||||
title.sub_folder = folder;
|
||||
|
||||
if(folder.empty()) // Add (move) it to root again
|
||||
{
|
||||
|
@ -62,11 +97,26 @@ namespace cfg
|
|||
list.folders.push_back(fld);
|
||||
}
|
||||
}
|
||||
|
||||
SaveRecord(title);
|
||||
}
|
||||
|
||||
return title_found;
|
||||
}
|
||||
|
||||
TitleFolder &FindFolderByName(TitleList &list, std::string name)
|
||||
{
|
||||
if(!name.empty())
|
||||
{
|
||||
auto f = STLITER_FINDWITHCONDITION(list.folders, fld, (fld.name == name));
|
||||
if(STLITER_ISFOUND(list.folders, f))
|
||||
{
|
||||
return STLITER_UNWRAP(f);
|
||||
}
|
||||
}
|
||||
return list.root;
|
||||
}
|
||||
|
||||
ResultWith<TitleList> LoadTitleList(bool cache)
|
||||
{
|
||||
TitleList list = {};
|
||||
|
@ -82,24 +132,25 @@ namespace cfg
|
|||
}
|
||||
else return MakeResultWith(rc, list);
|
||||
|
||||
auto [rc2, menu] = util::LoadJSONFromFile(Q_MENU_JSON);
|
||||
if(R_SUCCEEDED(rc2))
|
||||
fs::ForEachFileIn(Q_ENTRIES_PATH, [&](std::string name, std::string path)
|
||||
{
|
||||
for(auto &item: menu)
|
||||
auto [rc, entry] = util::LoadJSONFromFile(path);
|
||||
if(R_SUCCEEDED(rc))
|
||||
{
|
||||
TitleType type = (TitleType)item.value("type", 0u);
|
||||
TitleType type = (TitleType)entry.value("type", 0u);
|
||||
if(type == TitleType::Installed)
|
||||
{
|
||||
std::string appidstr = item.value("application_id", "");
|
||||
std::string appidstr = entry.value("application_id", "");
|
||||
if(!appidstr.empty())
|
||||
{
|
||||
std::string folder = item.value("folder", "");
|
||||
std::string folder = entry.value("folder", "");
|
||||
u64 appid = util::Get64FromString(appidstr);
|
||||
if(appid > 0)
|
||||
{
|
||||
if(!folder.empty())
|
||||
{
|
||||
TitleRecord rec = {};
|
||||
rec.json_name = name;
|
||||
rec.app_id = appid;
|
||||
rec.title_type = (u32)TitleType::Installed;
|
||||
|
||||
|
@ -127,15 +178,16 @@ namespace cfg
|
|||
}
|
||||
else if(type == TitleType::Homebrew)
|
||||
{
|
||||
std::string nropath = item.value("nro_path", "");
|
||||
std::string nropath = entry.value("nro_path", "");
|
||||
if(!nropath.empty())
|
||||
{
|
||||
TitleRecord rec = {};
|
||||
rec.json_name = name;
|
||||
rec.title_type = (u32)type;
|
||||
rec.nro_path = "sdmc:";
|
||||
if(nropath.front() != '/') rec.nro_path += "/";
|
||||
rec.nro_path += nropath;
|
||||
std::string folder = item.value("folder", "");
|
||||
std::string folder = entry.value("folder", "");
|
||||
rec.sub_folder = folder;
|
||||
if(cache)
|
||||
{
|
||||
|
@ -194,7 +246,8 @@ namespace cfg
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return SuccessResultWith(list);
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include <pu/Plutonium>
|
||||
#include <ui/ui_SideMenu.hpp>
|
||||
#include <ui/ui_RawData.hpp>
|
||||
#include <cfg/cfg_Config.hpp>
|
||||
|
||||
namespace ui
|
||||
{
|
||||
|
@ -13,13 +14,21 @@ namespace ui
|
|||
MenuLayout(void *raw);
|
||||
PU_SMART_CTOR(MenuLayout)
|
||||
|
||||
void menu_Click(u32 index);
|
||||
void menu_Click(u64 down, u32 index);
|
||||
void MoveFolder(std::string name, bool fade);
|
||||
void OnInput(u64 down, u64 up, u64 held, pu::ui::Touch pos);
|
||||
void SetSuspendedRawData(void *raw);
|
||||
|
||||
bool HandleFolderChange(cfg::TitleRecord &rec);
|
||||
private:
|
||||
void *susptr;
|
||||
SideMenu::Ref itemsMenu;
|
||||
RawData::Ref bgSuspendedRaw;
|
||||
std::string curfolder;
|
||||
std::chrono::steady_clock::time_point tp;
|
||||
bool warnshown;
|
||||
u32 root_idx;
|
||||
u32 root_baseidx;
|
||||
u32 mode;
|
||||
s32 rawalpha;
|
||||
};
|
||||
|
|
|
@ -17,6 +17,7 @@ namespace ui
|
|||
void LoadMenu();
|
||||
|
||||
bool IsTitleSuspended();
|
||||
bool LaunchFailed();
|
||||
void SetTitleSuspended(bool suspended);
|
||||
void SetSelectedUser(u128 user_id);
|
||||
u128 GetSelectedUser();
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
|
||||
#pragma once
|
||||
#include <pu/Plutonium>
|
||||
#include <map>
|
||||
|
||||
namespace ui
|
||||
{
|
||||
|
@ -11,9 +12,10 @@ namespace ui
|
|||
static constexpr u32 ItemCount = 4;
|
||||
static constexpr u32 FocusSize = 15;
|
||||
static constexpr u32 FocusMargin = 5;
|
||||
static constexpr u32 CursorSize = ItemSize + (Margin * 2);
|
||||
|
||||
public:
|
||||
SideMenu(pu::ui::Color FocusColor, pu::ui::Color SuspendedColor);
|
||||
SideMenu(pu::ui::Color SuspendedColor, std::string CursorPath);
|
||||
PU_SMART_CTOR(SideMenu)
|
||||
|
||||
s32 GetX() override;
|
||||
|
@ -22,8 +24,7 @@ namespace ui
|
|||
s32 GetHeight() override;
|
||||
void OnRender(pu::ui::render::Renderer::Ref &Drawer, s32 X, s32 Y) override;
|
||||
void OnInput(u64 Down, u64 Up, u64 Held, pu::ui::Touch Touch) override;
|
||||
void SetOnItemSelected(std::function<void(u32)> Fn);
|
||||
void SetOnItemFocus(std::function<void(u32)> Fn);
|
||||
void SetOnItemSelected(std::function<void(u64, u32)> Fn);
|
||||
void ClearItems();
|
||||
void AddItem(std::string Icon);
|
||||
void SetSuspendedItem(u32 Index);
|
||||
|
@ -33,20 +34,22 @@ namespace ui
|
|||
void HandleMoveRight();
|
||||
int GetSuspendedItem();
|
||||
u32 GetSelectedItem();
|
||||
private:
|
||||
u32 GetBaseItemIndex();
|
||||
void SetBaseItemIndex(u32 index);
|
||||
void ClearBorderIcons();
|
||||
void UpdateBorderIcons();
|
||||
private:
|
||||
s32 x;
|
||||
u32 selitm;
|
||||
u32 preselitm;
|
||||
int suspitm;
|
||||
u32 baseiconidx;
|
||||
u8 movalpha;
|
||||
pu::ui::Color selclr;
|
||||
pu::ui::Color suspclr;
|
||||
std::vector<std::string> icons;
|
||||
std::function<void(u32)> onfocus;
|
||||
std::function<void(u32)> onclick;
|
||||
std::function<void(u64, u32)> onselect;
|
||||
std::vector<pu::ui::render::NativeTexture> ricons;
|
||||
pu::ui::render::NativeTexture cursoricon;
|
||||
pu::ui::render::NativeTexture leftbicon;
|
||||
pu::ui::render::NativeTexture rightbicon;
|
||||
bool IsLeftFirst();
|
||||
|
|
BIN
LibraryAppletQMenu/RomFs/default/ui/Cursor.png
Normal file
BIN
LibraryAppletQMenu/RomFs/default/ui/Cursor.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.5 KiB |
|
@ -26,6 +26,7 @@ namespace qmenu
|
|||
db::Mount();
|
||||
fs::CreateDirectory(Q_BASE_DB_DIR);
|
||||
fs::CreateDirectory(Q_BASE_SD_DIR);
|
||||
fs::CreateDirectory(Q_ENTRIES_PATH);
|
||||
fs::CreateDirectory(Q_BASE_SD_DIR "/title");
|
||||
fs::CreateDirectory(Q_BASE_SD_DIR "/user");
|
||||
fs::CreateDirectory(Q_BASE_SD_DIR "/nro");
|
||||
|
@ -59,23 +60,22 @@ int main()
|
|||
auto renderer = pu::ui::render::Renderer::New(SDL_INIT_EVERYTHING, pu::ui::render::RendererInitOptions::RendererEverything, pu::ui::render::RendererHardwareFlags);
|
||||
qapp = ui::QMenuApplication::New(renderer);
|
||||
|
||||
u64 susappid = 0;
|
||||
|
||||
if(smode == am::QMenuStartMode::MenuApplicationSuspended)
|
||||
{
|
||||
am::QMenuCommandWriter writer(am::QDaemonMessage::GetSuspendedApplicationId);
|
||||
writer.FinishWrite();
|
||||
am::QMenuCommandResultReader reader;
|
||||
if(reader) qapp->SetSuspendedApplicationId(reader.Read<u64>());
|
||||
if(reader) susappid = reader.Read<u64>();
|
||||
reader.FinishRead();
|
||||
|
||||
FILE *f = fopen(Q_BASE_SD_DIR "/temp-suspended.rgba", "rb");
|
||||
if(f)
|
||||
{
|
||||
fread(app_buf, 1, 1280 * 720 * 4, f);
|
||||
fclose(f);
|
||||
}
|
||||
bool flag;
|
||||
appletGetLastApplicationCaptureImageEx(app_buf, 1280 * 720 * 4, &flag);
|
||||
}
|
||||
|
||||
qapp->SetStartMode(smode);
|
||||
qapp->SetSuspendedApplicationId(susappid);
|
||||
qapp->Prepare();
|
||||
|
||||
if(smode == am::QMenuStartMode::MenuApplicationSuspended) qapp->Show();
|
||||
|
|
|
@ -15,76 +15,152 @@ namespace ui
|
|||
this->susptr = raw;
|
||||
this->mode = 0;
|
||||
this->rawalpha = 255;
|
||||
this->root_idx = 0;
|
||||
this->warnshown = false;
|
||||
|
||||
this->bgSuspendedRaw = RawData::New(0, 0, raw, 1280, 720, 4);
|
||||
this->Add(this->bgSuspendedRaw);
|
||||
|
||||
this->itemsMenu = SideMenu::New(pu::ui::Color(0, 120, 255, 255), pu::ui::Color());
|
||||
this->itemsMenu = SideMenu::New(pu::ui::Color(0, 255, 120, 255), "romfs:/default/ui/Cursor.png");
|
||||
this->MoveFolder("", false);
|
||||
|
||||
this->itemsMenu->SetOnItemSelected(std::bind(&MenuLayout::menu_Click, this, std::placeholders::_1, std::placeholders::_2));
|
||||
this->Add(this->itemsMenu);
|
||||
this->tp = std::chrono::steady_clock::now();
|
||||
|
||||
this->SetOnInput(std::bind(&MenuLayout::OnInput, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4));
|
||||
}
|
||||
|
||||
void MenuLayout::menu_Click(u64 down, u32 index)
|
||||
{
|
||||
auto &folder = cfg::FindFolderByName(list, this->curfolder);
|
||||
if(index == 0)
|
||||
{
|
||||
if(down & KEY_A)
|
||||
{
|
||||
qapp->CreateShowDialog("A", "All titles...", {"Ok"}, true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
u32 realidx = index - 1;
|
||||
if(realidx < folder.titles.size())
|
||||
{
|
||||
auto title = folder.titles[realidx];
|
||||
if(down & KEY_A)
|
||||
{
|
||||
if(!qapp->IsTitleSuspended())
|
||||
{
|
||||
if((cfg::TitleType)title.title_type == cfg::TitleType::Homebrew)
|
||||
{
|
||||
// TODO: Homebrew launching!
|
||||
}
|
||||
else
|
||||
{
|
||||
am::QMenuCommandWriter writer(am::QDaemonMessage::LaunchApplication);
|
||||
writer.Write<u64>(title.app_id);
|
||||
writer.Write<bool>(false);
|
||||
writer.FinishWrite();
|
||||
|
||||
am::QMenuCommandResultReader reader;
|
||||
if(reader && R_SUCCEEDED(reader.GetReadResult()))
|
||||
{
|
||||
qapp->CloseWithFadeOut();
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto rc = reader.GetReadResult();
|
||||
qapp->CreateShowDialog("Title launch", "An error ocurred attempting to launch the title:\n" + util::FormatResultDisplay(rc) + " (" + util::FormatResultHex(rc) + ")", { "Ok" }, true);
|
||||
}
|
||||
reader.FinishRead();
|
||||
}
|
||||
}
|
||||
else if(qapp->GetSuspendedApplicationId() == title.app_id)
|
||||
{
|
||||
// Pressed A on the suspended title - return to it
|
||||
if(this->mode == 1) this->mode = 2;
|
||||
}
|
||||
}
|
||||
else if(down & KEY_X)
|
||||
{
|
||||
if(this->HandleFolderChange(title))
|
||||
{
|
||||
this->MoveFolder(this->curfolder, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto foldr = list.folders[realidx - folder.titles.size()];
|
||||
if(down & KEY_A)
|
||||
{
|
||||
this->MoveFolder(foldr.name, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MenuLayout::MoveFolder(std::string name, bool fade)
|
||||
{
|
||||
if(fade) qapp->FadeOut();
|
||||
|
||||
if(this->curfolder.empty())
|
||||
{
|
||||
// Moving from root to a folder, let's save the indexes we were on
|
||||
this->root_idx = itemsMenu->GetSelectedItem();
|
||||
this->root_baseidx = itemsMenu->GetBaseItemIndex();
|
||||
}
|
||||
|
||||
auto &folder = cfg::FindFolderByName(list, name);
|
||||
this->itemsMenu->ClearItems();
|
||||
|
||||
// Add first item for all titles menu
|
||||
this->itemsMenu->AddItem("romfs:/default/ui/AllTitles.png");
|
||||
|
||||
for(auto itm: list.root.titles)
|
||||
u32 tmpidx = 0;
|
||||
for(auto itm: folder.titles)
|
||||
{
|
||||
std::string iconpth;
|
||||
if((cfg::TitleType)itm.title_type == cfg::TitleType::Installed) iconpth = cfg::GetTitleCacheIconPath(itm.app_id);
|
||||
else if((cfg::TitleType)itm.title_type == cfg::TitleType::Homebrew) iconpth = cfg::GetNROCacheIconPath(itm.nro_path);
|
||||
this->itemsMenu->AddItem(iconpth);
|
||||
if(qapp->GetSuspendedApplicationId() == itm.app_id) this->itemsMenu->SetSuspendedItem(tmpidx + 1); // 1st item is always "all titles"!
|
||||
tmpidx++;
|
||||
}
|
||||
for(auto folder: list.folders)
|
||||
if(name.empty())
|
||||
{
|
||||
// TODO: folder logic
|
||||
this->itemsMenu->AddItem("romfs:/default/Folder.png");
|
||||
}
|
||||
this->itemsMenu->SetOnItemSelected(std::bind(&MenuLayout::menu_Click, this, std::placeholders::_1));
|
||||
this->Add(this->itemsMenu);
|
||||
|
||||
this->SetOnInput(std::bind(&MenuLayout::OnInput, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4));
|
||||
}
|
||||
|
||||
void MenuLayout::menu_Click(u32 index)
|
||||
{
|
||||
if(index == 0)
|
||||
{
|
||||
qapp->CreateShowDialog("A", "All titles", {"Ok"}, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
u32 realidx = index - 1;
|
||||
if(realidx < list.root.titles.size())
|
||||
std::vector<cfg::TitleFolder> folders;
|
||||
for(auto folder: list.folders)
|
||||
{
|
||||
auto title = list.root.titles[realidx];
|
||||
if(!qapp->IsTitleSuspended())
|
||||
if(!folder.titles.empty())
|
||||
{
|
||||
am::QMenuCommandWriter writer(am::QDaemonMessage::LaunchApplication);
|
||||
writer.Write<u64>(title.app_id);
|
||||
writer.Write<bool>(false);
|
||||
writer.FinishWrite();
|
||||
|
||||
am::QMenuCommandResultReader reader;
|
||||
if(reader && R_SUCCEEDED(reader.GetReadResult()))
|
||||
{
|
||||
qapp->CloseWithFadeOut();
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto rc = reader.GetReadResult();
|
||||
qapp->CreateShowDialog("Title launch", "An error ocurred attempting to launch the title:\n" + util::FormatResultDisplay(rc) + " (" + util::FormatResultHex(rc) + ")", { "Ok" }, true);
|
||||
}
|
||||
reader.FinishRead();
|
||||
folders.push_back(folder);
|
||||
this->itemsMenu->AddItem("romfs:/default/ui/Folder.png");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto folder = list.folders[realidx - list.root.titles.size()];
|
||||
qapp->CreateShowDialog("Folder", folder.name, {"Ok"}, true);
|
||||
}
|
||||
list.folders = folders;
|
||||
this->itemsMenu->SetSelectedItem(this->root_idx);
|
||||
this->itemsMenu->SetBaseItemIndex(this->root_baseidx);
|
||||
}
|
||||
this->itemsMenu->UpdateBorderIcons();
|
||||
|
||||
this->curfolder = name;
|
||||
|
||||
if(fade) qapp->FadeIn();
|
||||
}
|
||||
|
||||
void MenuLayout::OnInput(u64 down, u64 up, u64 held, pu::ui::Touch pos)
|
||||
{
|
||||
auto ctp = std::chrono::steady_clock::now();
|
||||
if(std::chrono::duration_cast<std::chrono::milliseconds>(ctp - this->tp).count() >= 500)
|
||||
{
|
||||
if(qapp->LaunchFailed() && !this->warnshown)
|
||||
{
|
||||
qapp->CreateShowDialog("Title launch", "The title failed to start.\nAre you sure it can be launched? (it isn't deleted, gamecard is inserted...)", {"Ok"}, true);
|
||||
this->warnshown = true;
|
||||
}
|
||||
}
|
||||
|
||||
auto [rc, msg] = am::QMenu_GetLatestQMenuMessage();
|
||||
switch(msg)
|
||||
{
|
||||
|
@ -103,7 +179,6 @@ namespace ui
|
|||
|
||||
if(this->susptr != NULL)
|
||||
{
|
||||
|
||||
if(this->mode == 0)
|
||||
{
|
||||
if(this->rawalpha == 80) this->mode = 1;
|
||||
|
@ -161,5 +236,44 @@ namespace ui
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(down & KEY_B)
|
||||
{
|
||||
if(!this->curfolder.empty()) this->MoveFolder("", true);
|
||||
}
|
||||
else if(down & KEY_L)
|
||||
{
|
||||
qapp->CreateShowDialog("A", "Id -> " + util::FormatApplicationId(qapp->GetSuspendedApplicationId()), {"Ok"}, true);
|
||||
}
|
||||
}
|
||||
|
||||
bool MenuLayout::HandleFolderChange(cfg::TitleRecord &rec)
|
||||
{
|
||||
bool changedone = false;
|
||||
|
||||
if(this->curfolder.empty())
|
||||
{
|
||||
SwkbdConfig swkbd;
|
||||
swkbdCreate(&swkbd, 0);
|
||||
swkbdConfigSetHeaderText(&swkbd, "Select directory name");
|
||||
char dir[500] = {0};
|
||||
auto rc = swkbdShow(&swkbd, dir, 500);
|
||||
if(R_SUCCEEDED(rc))
|
||||
{
|
||||
cfg::MoveTitleToDirectory(list, rec.app_id, std::string(dir));
|
||||
changedone = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto sopt = qapp->CreateShowDialog("Title move", "Would you like to move this entry outside the folder?", { "Yes", "Cancel" }, true);
|
||||
if(sopt == 0)
|
||||
{
|
||||
cfg::MoveTitleToDirectory(list, rec.app_id, "");
|
||||
changedone = true;
|
||||
}
|
||||
}
|
||||
|
||||
return changedone;
|
||||
}
|
||||
}
|
|
@ -21,6 +21,9 @@ namespace ui
|
|||
this->SetTitleSuspended(true);
|
||||
this->LoadMenu();
|
||||
break;
|
||||
case am::QMenuStartMode::MenuLaunchFailure:
|
||||
this->LoadMenu();
|
||||
break;
|
||||
default:
|
||||
this->LoadLayout(this->startupLayout);
|
||||
break;
|
||||
|
@ -42,6 +45,11 @@ namespace ui
|
|||
return this->tsuspended;
|
||||
}
|
||||
|
||||
bool QMenuApplication::LaunchFailed()
|
||||
{
|
||||
return (this->stmode == am::QMenuStartMode::MenuLaunchFailure);
|
||||
}
|
||||
|
||||
void QMenuApplication::SetTitleSuspended(bool suspended)
|
||||
{
|
||||
this->tsuspended = suspended;
|
||||
|
@ -63,7 +71,7 @@ namespace ui
|
|||
|
||||
void QMenuApplication::SetSuspendedApplicationId(u64 app_id)
|
||||
{
|
||||
this->SetTitleSuspended(true);
|
||||
this->SetTitleSuspended(app_id != 0);
|
||||
this->suspended_appid = app_id;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,18 +2,17 @@
|
|||
|
||||
namespace ui
|
||||
{
|
||||
SideMenu::SideMenu(pu::ui::Color FocusColor, pu::ui::Color SuspendedColor)
|
||||
SideMenu::SideMenu(pu::ui::Color SuspendedColor, std::string CursorPath)
|
||||
{
|
||||
this->selitm = 0;
|
||||
this->suspitm = -1;
|
||||
this->selclr = FocusColor;
|
||||
this->suspclr = SuspendedColor;
|
||||
this->baseiconidx = 0;
|
||||
this->movalpha = 0;
|
||||
this->leftbicon = NULL;
|
||||
this->rightbicon = NULL;
|
||||
this->onfocus = [&](u32){};
|
||||
this->onclick = [&](u32){};
|
||||
this->cursoricon = pu::ui::render::LoadImage(CursorPath);
|
||||
this->onselect = [&](u32,u64){};
|
||||
}
|
||||
|
||||
s32 SideMenu::GetX()
|
||||
|
@ -38,9 +37,9 @@ namespace ui
|
|||
if(this->icons.empty()) return;
|
||||
if(this->ricons.empty())
|
||||
{
|
||||
for(u32 i = 0; i < std::min((size_t)4, this->icons.size()); i++)
|
||||
for(u32 i = 0; i < std::min((size_t)4, (this->icons.size() - this->baseiconidx)); i++)
|
||||
{
|
||||
auto icon = pu::ui::render::LoadImage(this->icons[i]);
|
||||
auto icon = pu::ui::render::LoadImage(this->icons[this->baseiconidx + i]);
|
||||
this->ricons.push_back(icon);
|
||||
}
|
||||
this->UpdateBorderIcons();
|
||||
|
@ -52,17 +51,24 @@ namespace ui
|
|||
{
|
||||
auto ricon = this->ricons[i];
|
||||
Drawer->RenderTexture(ricon, basex, Y, { -1, ItemSize, ItemSize, -1 });
|
||||
pu::ui::Color clr = this->selclr;
|
||||
clr.A = 175 - movalpha;
|
||||
pu::ui::Color preclr = this->selclr;
|
||||
preclr.A = movalpha;
|
||||
if((this->baseiconidx + i) == selitm)
|
||||
|
||||
if(this->cursoricon != NULL)
|
||||
{
|
||||
Drawer->RenderRectangleFill(clr, basex, Y, ItemSize, ItemSize);
|
||||
if((this->baseiconidx + i) == selitm)
|
||||
{
|
||||
Drawer->RenderTexture(this->cursoricon, basex - Margin, Y - Margin, { 255 - movalpha, CursorSize, CursorSize, -1 });
|
||||
}
|
||||
else if((this->baseiconidx + i) == preselitm)
|
||||
{
|
||||
Drawer->RenderTexture(this->cursoricon, basex - Margin, Y - Margin, { movalpha, CursorSize, CursorSize, -1 });
|
||||
}
|
||||
}
|
||||
else if((this->baseiconidx + i) == preselitm)
|
||||
if(this->suspitm >= 0)
|
||||
{
|
||||
Drawer->RenderRectangleFill(preclr, basex, Y, ItemSize, ItemSize);
|
||||
if((this->baseiconidx + i) == (u32)suspitm)
|
||||
{
|
||||
Drawer->RenderRectangleFill(this->suspclr, basex, Y + ItemSize + FocusMargin, ItemSize, FocusSize);
|
||||
}
|
||||
}
|
||||
basex += ItemSize + Margin;
|
||||
}
|
||||
|
@ -91,17 +97,12 @@ namespace ui
|
|||
|
||||
if(Down & KEY_LEFT) HandleMoveLeft();
|
||||
else if(Down & KEY_RIGHT) HandleMoveRight();
|
||||
else if(Down & KEY_A) (this->onclick)(selitm);
|
||||
else (this->onselect)(Down, this->selitm);
|
||||
}
|
||||
|
||||
void SideMenu::SetOnItemSelected(std::function<void(u32)> Fn)
|
||||
void SideMenu::SetOnItemSelected(std::function<void(u64, u32)> Fn)
|
||||
{
|
||||
this->onclick = Fn;
|
||||
}
|
||||
|
||||
void SideMenu::SetOnItemFocus(std::function<void(u32)> Fn)
|
||||
{
|
||||
this->onfocus = Fn;
|
||||
this->onselect = Fn;
|
||||
}
|
||||
|
||||
void SideMenu::ClearItems()
|
||||
|
@ -110,7 +111,9 @@ namespace ui
|
|||
for(auto &ricon: this->ricons) pu::ui::render::DeleteTexture(ricon);
|
||||
this->ricons.clear();
|
||||
this->selitm = 0;
|
||||
this->baseiconidx = 0;
|
||||
this->suspitm = -1;
|
||||
this->ClearBorderIcons();
|
||||
}
|
||||
|
||||
void SideMenu::AddItem(std::string Icon)
|
||||
|
@ -141,8 +144,7 @@ namespace ui
|
|||
preselitm = selitm;
|
||||
selitm--;
|
||||
if(ilf) ReloadIcons(1);
|
||||
else movalpha = 175;
|
||||
(this->onfocus)(selitm);
|
||||
else movalpha = 255;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -154,8 +156,7 @@ namespace ui
|
|||
preselitm = selitm;
|
||||
selitm++;
|
||||
if(irl) ReloadIcons(2);
|
||||
else movalpha = 175;
|
||||
(this->onfocus)(selitm);
|
||||
else movalpha = 255;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -224,16 +225,31 @@ namespace ui
|
|||
this->UpdateBorderIcons();
|
||||
}
|
||||
|
||||
void SideMenu::UpdateBorderIcons()
|
||||
u32 SideMenu::GetBaseItemIndex()
|
||||
{
|
||||
return this->baseiconidx;
|
||||
}
|
||||
|
||||
void SideMenu::SetBaseItemIndex(u32 index)
|
||||
{
|
||||
this->baseiconidx = index;
|
||||
}
|
||||
|
||||
void SideMenu::ClearBorderIcons()
|
||||
{
|
||||
if(leftbicon != NULL) pu::ui::render::DeleteTexture(leftbicon);
|
||||
leftbicon = NULL;
|
||||
if(rightbicon != NULL) pu::ui::render::DeleteTexture(rightbicon);
|
||||
rightbicon = NULL;
|
||||
}
|
||||
|
||||
void SideMenu::UpdateBorderIcons()
|
||||
{
|
||||
this->ClearBorderIcons();
|
||||
if(baseiconidx > 0)
|
||||
{
|
||||
leftbicon = pu::ui::render::LoadImage(icons[baseiconidx - 1]);
|
||||
}
|
||||
if(rightbicon != NULL) pu::ui::render::DeleteTexture(rightbicon);
|
||||
rightbicon = NULL;
|
||||
if((baseiconidx + 4) < icons.size())
|
||||
{
|
||||
rightbicon = pu::ui::render::LoadImage(icons[baseiconidx + 4]);
|
||||
|
|
|
@ -85,15 +85,6 @@ void HandleAppletMessage()
|
|||
{
|
||||
if(am::ApplicationHasForeground())
|
||||
{
|
||||
bool flag;
|
||||
appletUpdateLastForegroundCaptureImage();
|
||||
appletGetLastForegroundCaptureImageEx(app_buf, 1280 * 720 * 4, &flag);
|
||||
FILE *f = fopen(Q_BASE_SD_DIR "/temp-suspended.rgba", "wb");
|
||||
if(f)
|
||||
{
|
||||
fwrite(app_buf, 1, 1280 * 720 * 4, f);
|
||||
fclose(f);
|
||||
}
|
||||
am::HomeMenuSetForeground();
|
||||
am::QDaemon_LaunchQMenu(am::QMenuStartMode::MenuApplicationSuspended);
|
||||
used_to_reopen_menu = true;
|
||||
|
@ -307,7 +298,12 @@ int main()
|
|||
if(!am::LibraryAppletIsActive())
|
||||
{
|
||||
auto rc = am::ApplicationStart(titlelaunch_flag, titlelaunch_system, selected_uid);
|
||||
if(R_FAILED(rc)) am::QDaemon_LaunchQMenu(am::QMenuStartMode::StartupScreen);
|
||||
svcSleepThread(500'000'000);
|
||||
if(!am::ApplicationIsActive())
|
||||
{
|
||||
// Title failed to launch, so we re-launch QMenu this way...
|
||||
am::QDaemon_LaunchQMenu(am::QMenuStartMode::MenuLaunchFailure);
|
||||
}
|
||||
titlelaunch_flag = 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -78,8 +78,10 @@ Can be customized via files in `/ui`.
|
|||
|
||||
- `ui/Font.ttf` -> TTF font used for all the UI.
|
||||
|
||||
- `ui/AllTitles.png` -> 256x256 icon for the "all titles" entry in the main menu.
|
||||
- `ui/AllTitles.png` -> 256x256 PNG for the "all titles" entry in the main menu.
|
||||
|
||||
- `ui/Folder.png` -> 256x256 icon for folders in the main menu.
|
||||
- `ui/Folder.png` -> 256x256 PNG for folders in the main menu.
|
||||
|
||||
- `ui/Cursor.png` -> 296x296 PNG for the cursor pointing at items in the main menu.
|
||||
|
||||
> *TODO: more customizable stuff*
|
Loading…
Reference in a new issue