From d1ef9de97947bcba8baf59a3cb3617a8b9023572 Mon Sep 17 00:00:00 2001 From: XorTroll Date: Sat, 12 Oct 2019 15:43:56 +0200 Subject: [PATCH] Add folders, failed launch handling and more --- Common/Include/am/am_QCommunications.hpp | 3 +- Common/Include/cfg/cfg_Config.hpp | 3 + Common/Include/os/os_Titles.hpp | 6 + Common/Include/q_Include.hpp | 3 +- Common/Source/cfg/cfg_Config.cpp | 73 +++++- .../Include/ui/ui_MenuLayout.hpp | 11 +- .../Include/ui/ui_QMenuApplication.hpp | 1 + LibraryAppletQMenu/Include/ui/ui_SideMenu.hpp | 17 +- .../RomFs/default/ui/Cursor.png | Bin 0 -> 3583 bytes LibraryAppletQMenu/Source/Main.cpp | 14 +- .../Source/ui/ui_MenuLayout.cpp | 210 ++++++++++++++---- .../Source/ui/ui_QMenuApplication.cpp | 10 +- LibraryAppletQMenu/Source/ui/ui_SideMenu.cpp | 74 +++--- SystemAppletQDaemon/Source/Main.cpp | 16 +- Themes.md | 6 +- 15 files changed, 329 insertions(+), 118 deletions(-) create mode 100644 LibraryAppletQMenu/RomFs/default/ui/Cursor.png diff --git a/Common/Include/am/am_QCommunications.hpp b/Common/Include/am/am_QCommunications.hpp index 57de355..a275c73 100644 --- a/Common/Include/am/am_QCommunications.hpp +++ b/Common/Include/am/am_QCommunications.hpp @@ -9,7 +9,8 @@ namespace am Invalid, StartupScreen, MenuNormal, - MenuApplicationSuspended + MenuApplicationSuspended, + MenuLaunchFailure }; enum class QMenuMessage diff --git a/Common/Include/cfg/cfg_Config.hpp b/Common/Include/cfg/cfg_Config.hpp index cfc1e2c..64f67ca 100644 --- a/Common/Include/cfg/cfg_Config.hpp +++ b/Common/Include/cfg/cfg_Config.hpp @@ -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 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); diff --git a/Common/Include/os/os_Titles.hpp b/Common/Include/os/os_Titles.hpp index 966aabf..979a7ad 100644 --- a/Common/Include/os/os_Titles.hpp +++ b/Common/Include/os/os_Titles.hpp @@ -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> QueryInstalledTitles(bool cache); } \ No newline at end of file diff --git a/Common/Include/q_Include.hpp b/Common/Include/q_Include.hpp index f573abe..c956450 100644 --- a/Common/Include/q_Include.hpp +++ b/Common/Include/q_Include.hpp @@ -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) \ diff --git a/Common/Source/cfg/cfg_Config.cpp b/Common/Source/cfg/cfg_Config.cpp index 50938d0..3fa39cc 100644 --- a/Common/Source/cfg/cfg_Config.cpp +++ b/Common/Source/cfg/cfg_Config.cpp @@ -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 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); } diff --git a/LibraryAppletQMenu/Include/ui/ui_MenuLayout.hpp b/LibraryAppletQMenu/Include/ui/ui_MenuLayout.hpp index 1053eb2..deeaf11 100644 --- a/LibraryAppletQMenu/Include/ui/ui_MenuLayout.hpp +++ b/LibraryAppletQMenu/Include/ui/ui_MenuLayout.hpp @@ -4,6 +4,7 @@ #include #include #include +#include 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; }; diff --git a/LibraryAppletQMenu/Include/ui/ui_QMenuApplication.hpp b/LibraryAppletQMenu/Include/ui/ui_QMenuApplication.hpp index e2727f1..e4c80d6 100644 --- a/LibraryAppletQMenu/Include/ui/ui_QMenuApplication.hpp +++ b/LibraryAppletQMenu/Include/ui/ui_QMenuApplication.hpp @@ -17,6 +17,7 @@ namespace ui void LoadMenu(); bool IsTitleSuspended(); + bool LaunchFailed(); void SetTitleSuspended(bool suspended); void SetSelectedUser(u128 user_id); u128 GetSelectedUser(); diff --git a/LibraryAppletQMenu/Include/ui/ui_SideMenu.hpp b/LibraryAppletQMenu/Include/ui/ui_SideMenu.hpp index 444ceb8..43a09d2 100644 --- a/LibraryAppletQMenu/Include/ui/ui_SideMenu.hpp +++ b/LibraryAppletQMenu/Include/ui/ui_SideMenu.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include 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 Fn); - void SetOnItemFocus(std::function Fn); + void SetOnItemSelected(std::function 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 icons; - std::function onfocus; - std::function onclick; + std::function onselect; std::vector ricons; + pu::ui::render::NativeTexture cursoricon; pu::ui::render::NativeTexture leftbicon; pu::ui::render::NativeTexture rightbicon; bool IsLeftFirst(); diff --git a/LibraryAppletQMenu/RomFs/default/ui/Cursor.png b/LibraryAppletQMenu/RomFs/default/ui/Cursor.png new file mode 100644 index 0000000000000000000000000000000000000000..c07168e0ca1ce453b315cec7508ea6ed66c1c77f GIT binary patch literal 3583 zcmbtW2~<cHwNDY$1@k zOvSA$P}zL%bq6c~0@?rK!;tHZEDw-n$~lL&b&vL4=EhbXBVy%$v0h$JfbVrkdwnq^ zG({>WXL`v0ls%x+)Q&!I=urCJqfy7t0rB=v;EV@*>=k|*@BFI3Wb*Ha&w)aUEfpXsD1E>@MaEIvv_g9yJe#&KW3<*69%75XGLOs54OPZUk9WxR3z?T z5z~iVH?h*5y~dvAu*0aOELC0%ye4z%fMnXiv<#ut)DX(?`OHE(!gPbfFd6ps0a~eB zp`an6*-p#Cm`Y3INkb!+f}*e$^pZk~sZO(f>80~$x2^GnoDrX8r_s6_(AU*&lEfnZ z02SVnRoYG5=CKmQ*T5$L5mR)xB!#68Rqf7ET=xgGi)NmEe-pzjnG|RIh;OQ(Pp2y(0X}tZftiX(;a$gGuz-*qJp_tm^OdWt zg>~^+z;S_CTQ#M7atG zNhKnV=p*Gdsts*e{$x=OFx1?T7FbSq{?6@U-XhP)8tYmdj8p4(wVh9nt6l5n$9de65p;35aB^Q2>sS5XsCma29x+`%?nXTzdH z$KJUnHQMM^h~rxt&|b*wAi?-*tp(3u{y=3Y;~`(bGY+KAzS(|;SmyfSu>Uz_t4Gt zZ`l9o1p_EF=MnwZjbBxi?wfu5u2)fU&5hqN`mHlDE0g?K)x#JvdS%yduuq|%9q^YY zKf?Mh{Fcu%i3a}IC~L0Y6aEY7cIMAWZdj16#i{V$YoMjai{fVGLXF~g{GRGloBlD= zr~i!cBFH_3>G75mPI8H>x!egPQdjXs)H{4YmEu@z9aUiD?kb&f>iy+$ghE)J(b%#} z)Ub7;!dpRvWnxULUab$FtL4Az9+fDJ#BHeSm)oSL)d}Ay%bH%dm2|mn%MP@GQ*+%T zraNkA+{$Kpi95dYC*jlP(Of8zV5JyFn^;Qz^<58B=T5#Zq8Xhr6uC@z=`3ot-)^G$ z^q;VIshtmMXq)91+l}fyHYnr3iM`?h;do>%y~JB2?+cUsb1P$hevWcNONv=`y4>IK zc~)Bj9{qG+!B;@&6SDDSLclLZ^6B*Ye=rVa^AVjIB&5))3kDzSo}*ND><#OfSaJtT z@5h86B`VSG7N#tnEHZf1ZkWou%?yP{ruEV^)U6+3hgWmP>bm6zriag1>)0HTBs0bE zJj?~Htm}SY22;KbfEk3ZPe&w*{f4~{`T40ySWGd(1O4g_;DSLNU}?_wZou3K{^kw+ zub@y|N<4f`b>ucR8nT%6*UstxJ;eVo$Sx@51raUG_(70CO~jum_Zv|f7J4G~(?9#a zga3>DzmxT^@b5n?T~Fq$fBpGlq>8#DtU6f4PXJO|c~BDO3+{XX$U-#CSKwNNA#raW z0epmE@)<*X;L+G)ct5ZPLF`pR0mL23!zNgVnM0&+!Eb;?bde@&hvX920LYBs?v@AG zRc~i%(Em8NI&%s}LW-h67L#{U7txDQ!l0B%!5a>j^$Qke9oD;c0t>eSa9b|mD03Lj zRj$(49yh@tI*&-m0YtsPDCv*10Bm4#2crITcCEUX zE~4ncK{6+bL`Nx<3+0yw2REz_lW-ehKQ{HTTT4lf|EN*g?-yaDmUl+n^TXO?}5}d z-0r4xR^(RmS7UPhI&HIPa8iKX_-xW8r+9RJdgBJMZgI}o1LF%2AznvYkF}o??X+mP zy_EbA7-L5+pLTaOM~e_IoaV)w?n+H=`vZEiXbqRk>Gi5(n%2u3@3|`DCk@|}Qv0g0 z!OFsdh}L8pyB)`ll;bQ^$OLIs)jhZGGb+(4@%(`=I_DV=PluGB>6vWjsM*O?4ZPWn z46UOFHAohd5EtTW!_81EqHUxLQt3}>`ELZ|Th8Hss5>bxX@jJx)!y@)aXCxx)tko{lCFKsx*=xrkdlja_gSj$D7b6iIwT& zcjlV?19tq&{4|cT)0~YHWKdf{!P95x8t!bzLnA1(Y7Pq+Ilg_yd`_V^eB-)*WY@)@ zKbS&aeYS?@*f_?|FqXccCVd7;s!Nvt;4S~E*bDlh(E9D!YX#XWYtPqvkVTpn(}UGu zKPIR(UF;d7*{HE+38ceT>kZcf8Ic2v=QleIT@G)D1ytD%=E29h-Z2*U#BcjWY{kDL zn>$(A5H^OiE;&g01=0qFki9u7VN9PcKRe^^b~5kgKp8^&?$i;BCurVllNJ%mR8+xS z17PUtyV1`KEX$y;J$5Yi2|C4=ogOHi?6De2FVoO&n?mvW8kqdrt=gt0W6Ks4-G%i& zBP)hni*6cBiTQr3_i+~Y-S$>3XxtB*u>)ZCs6#Cz3MVi<*yoTWn^}xuAhK$X>~F3u z?3tt#R(Mk+S8v9E9`N%aZztKU9JmdvjuOt?G3DJv$tcIW340MRy@v7I~ZoNi9X zb8@*RvBo9zQE>wEISP>NQoce^=?)QfOUwal&QD+UY$B!$FXr*tG$0|+yfJ$#e`QS! z;LxI&rDY#y-={yf9L!73FDYdW7olJ+I$1v zC5IN+q}6mDU>)~8Wzf%i0UWtX<8e>mUtZarX)h{&lZg>Mi`qWX7nmhUVh*8mlzD7` zcH#h^UE-}nO5jfUZHk!xL~(wO5AkT68tJc;n^q?s&9bU9_$BhN8)IiXLX^eyxTj38 zP`}Tn7%hnQZQpi?5IMbfLP-S^+StjKnssxTQ{yeaeMu(#s{XoYwE!0!7YeQcVeOR& zlHe;83U+h*WmuQSjpc33_^LM8=a75tsi}0^f%1wJ1Ny;r?TL;u}_N$R7<7#86WkwxCezZtA_O){m}QuL*}Ex q+_)m)?&X*67IGg9r!Xl)Lt?`S)*kv2I;Ztv<(!=}y86q@@&656#>E~0 literal 0 HcmV?d00001 diff --git a/LibraryAppletQMenu/Source/Main.cpp b/LibraryAppletQMenu/Source/Main.cpp index dea5da2..dda8b2d 100644 --- a/LibraryAppletQMenu/Source/Main.cpp +++ b/LibraryAppletQMenu/Source/Main.cpp @@ -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()); + if(reader) susappid = reader.Read(); 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(); diff --git a/LibraryAppletQMenu/Source/ui/ui_MenuLayout.cpp b/LibraryAppletQMenu/Source/ui/ui_MenuLayout.cpp index 43cbd91..a45ef85 100644 --- a/LibraryAppletQMenu/Source/ui/ui_MenuLayout.cpp +++ b/LibraryAppletQMenu/Source/ui/ui_MenuLayout.cpp @@ -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(title.app_id); + writer.Write(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 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(title.app_id); - writer.Write(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(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; } } \ No newline at end of file diff --git a/LibraryAppletQMenu/Source/ui/ui_QMenuApplication.cpp b/LibraryAppletQMenu/Source/ui/ui_QMenuApplication.cpp index 1eafbd9..43b39d0 100644 --- a/LibraryAppletQMenu/Source/ui/ui_QMenuApplication.cpp +++ b/LibraryAppletQMenu/Source/ui/ui_QMenuApplication.cpp @@ -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; } diff --git a/LibraryAppletQMenu/Source/ui/ui_SideMenu.cpp b/LibraryAppletQMenu/Source/ui/ui_SideMenu.cpp index cf26614..85558b4 100644 --- a/LibraryAppletQMenu/Source/ui/ui_SideMenu.cpp +++ b/LibraryAppletQMenu/Source/ui/ui_SideMenu.cpp @@ -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 Fn) + void SideMenu::SetOnItemSelected(std::function Fn) { - this->onclick = Fn; - } - - void SideMenu::SetOnItemFocus(std::function 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]); diff --git a/SystemAppletQDaemon/Source/Main.cpp b/SystemAppletQDaemon/Source/Main.cpp index f60c825..38877e1 100644 --- a/SystemAppletQDaemon/Source/Main.cpp +++ b/SystemAppletQDaemon/Source/Main.cpp @@ -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; } } diff --git a/Themes.md b/Themes.md index 31fe94d..7477dda 100644 --- a/Themes.md +++ b/Themes.md @@ -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* \ No newline at end of file