InstalledList.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. #define FTS_FUZZY_MATCH_IMPLEMENTATION
  2. #include <cmath>
  3. #include <TweenEngine/Tween.h>
  4. #include <cpp3ds/System/Lock.hpp>
  5. #include "InstalledList.hpp"
  6. #include "AppList.hpp"
  7. #include "TitleKeys.hpp"
  8. #include "fts_fuzzy_match.h"
  9. #ifndef EMULATION
  10. #include "KeyboardApplet.hpp"
  11. #endif
  12. namespace FreeShop {
  13. InstalledList::InstalledList()
  14. : m_scrollPos(0.f)
  15. , m_size(320.f, 0.f)
  16. , m_expandedItem(nullptr)
  17. , m_isUpdatingList(false)
  18. , m_gameCount(0)
  19. , m_sortType(Name)
  20. , m_sortAscending(true)
  21. , m_canDrawList(true)
  22. {
  23. // Make install options initially transparent for fade in
  24. TweenEngine::Tween::set(m_options, InstalledOptions::ALPHA)
  25. .target(0.f).start(m_tweenManager);
  26. m_currentSearch = "";
  27. }
  28. InstalledList &InstalledList::getInstance()
  29. {
  30. static InstalledList installedList;
  31. return installedList;
  32. }
  33. void InstalledList::refresh()
  34. {
  35. cpp3ds::Lock lock(m_mutexRefresh);
  36. cpp3ds::Uint64 relatedTitleId;
  37. std::vector<cpp3ds::Uint64> installedTitleIds;
  38. m_isUpdatingList = true;
  39. m_installedTitleIds.clear();
  40. m_installedItems.clear();
  41. m_expandedItem = nullptr;
  42. TweenEngine::Tween::set(m_options, InstalledOptions::ALPHA).target(0.f).start(m_tweenManager);
  43. #ifdef EMULATION
  44. // some hardcoded title IDs for testing
  45. installedTitleIds.emplace_back(0x00040000000edf00); // [US] Super Smash Bros.
  46. installedTitleIds.emplace_back(0x0004000e000edf00); // [US] Super Smash Bros. [UPDATE]
  47. installedTitleIds.emplace_back(0x0004008c000edf00); // [US] Super Smash Bros. [DLC]
  48. installedTitleIds.emplace_back(0x00040002000edf01); // [US] Super Smash Bros. [DEMO]
  49. installedTitleIds.emplace_back(0x0004001000021800); // [US] StreetPass Mii Plaza
  50. installedTitleIds.emplace_back(0x0004000000030800); // [US] Mario Kart 7
  51. installedTitleIds.emplace_back(0x0004000000041700); // [US] Kirby's Dream Land
  52. installedTitleIds.emplace_back(0x0004008000008f00); // [US] Home Menu
  53. installedTitleIds.emplace_back(0x0004000000162000); // [EU] Project X Zone 2
  54. installedTitleIds.emplace_back(0x000400000017a400); // [EU] Fire Emblem Fates
  55. installedTitleIds.emplace_back(0x00040000001b7800); // [US] Pic-a-Pix Color
  56. #else
  57. u32 titleCount;
  58. AM_GetTitleCount(MEDIATYPE_SD, &titleCount);
  59. installedTitleIds.resize(titleCount);
  60. AM_GetTitleList(nullptr, MEDIATYPE_SD, titleCount, &installedTitleIds[0]);
  61. AM_GetTitleCount(MEDIATYPE_NAND, &titleCount);
  62. installedTitleIds.resize(titleCount + installedTitleIds.size());
  63. AM_GetTitleList(nullptr, MEDIATYPE_NAND, titleCount, &installedTitleIds[installedTitleIds.size() - titleCount]);
  64. FS_CardType type;
  65. bool cardInserted;
  66. m_cardInserted = (R_SUCCEEDED(FSUSER_CardSlotIsInserted(&cardInserted)) && cardInserted && R_SUCCEEDED(FSUSER_GetCardType(&type)) && type == CARD_CTR);
  67. if (m_cardInserted)
  68. {
  69. // Retry a bunch of times. When the card is newly inserted,
  70. // it sometimes takes a short while before title can be read.
  71. int retryCount = 100;
  72. u64 cardTitleId;
  73. while (retryCount-- > 0)
  74. if (R_SUCCEEDED(AM_GetTitleList(nullptr, MEDIATYPE_GAME_CARD, 1, &cardTitleId)))
  75. {
  76. try
  77. {
  78. std::shared_ptr<InstalledItem> item(new InstalledItem(cardTitleId));
  79. m_installedItems.emplace_back(std::move(item));
  80. }
  81. catch (int e)
  82. {
  83. //
  84. }
  85. break;
  86. }
  87. else
  88. cpp3ds::sleep(cpp3ds::milliseconds(5));
  89. }
  90. #endif
  91. for (auto& titleId : installedTitleIds)
  92. {
  93. size_t titleType = titleId >> 32;
  94. if (titleType == TitleKeys::Game || titleType == TitleKeys::Update ||
  95. titleType == TitleKeys::DLC || titleType == TitleKeys::Demo || titleType == TitleKeys::DSiWare ||
  96. titleType == TitleKeys::SystemApplet || titleType == TitleKeys::SystemApplication)
  97. m_installedTitleIds.push_back(titleId);
  98. }
  99. // Add all primary game titles first
  100. for (auto& titleId : installedTitleIds)
  101. {
  102. TitleKeys::TitleType titleType = static_cast<TitleKeys::TitleType>(titleId >> 32);
  103. if (titleType == TitleKeys::Game || titleType == TitleKeys::DSiWare || titleType == TitleKeys::SystemApplication || titleType == TitleKeys::SystemApplet)
  104. try
  105. {
  106. std::shared_ptr<InstalledItem> item(new InstalledItem(titleId));
  107. m_installedItems.emplace_back(std::move(item));
  108. }
  109. catch (int e)
  110. {
  111. //std::cout << "Error while adding: " << titleId << std::endl;
  112. }
  113. }
  114. // Add updates that have not yet been installed for which we have a titlekey
  115. for (auto& titleId : TitleKeys::getIds())
  116. {
  117. size_t titleType = titleId >> 32;
  118. if (titleType == TitleKeys::Update || titleType == TitleKeys::DLC)
  119. {
  120. size_t titleLower = (titleId & 0xFFFFFFFF) >> 8;
  121. for (auto& installedItem : m_installedItems)
  122. {
  123. if (titleLower == (installedItem->getTitleId() & 0xFFFFFFFF) >> 8)
  124. {
  125. if (titleType == TitleKeys::Update)
  126. installedItem->setUpdateStatus(titleId, false);
  127. else
  128. installedItem->setDLCStatus(titleId, false);
  129. }
  130. }
  131. }
  132. }
  133. // Add all installed updates/DLC to the above titles added.
  134. // If not found, attempt to fetch parent title info from system.
  135. for (auto& titleId : installedTitleIds)
  136. {
  137. TitleKeys::TitleType titleType = static_cast<TitleKeys::TitleType>(titleId >> 32);
  138. if (titleType == TitleKeys::Update || titleType == TitleKeys::DLC)
  139. {
  140. cpp3ds::Uint32 titleLower = (titleId & 0xFFFFFFFF) >> 8;
  141. for (auto& installedItem : m_installedItems)
  142. {
  143. if (titleLower == (installedItem->getTitleId() & 0xFFFFFFFF) >> 8)
  144. {
  145. if (titleType == TitleKeys::Update)
  146. installedItem->setUpdateStatus(titleId, true);
  147. else
  148. installedItem->setDLCStatus(titleId, true);
  149. break;
  150. }
  151. }
  152. }
  153. }
  154. // Remove all system titles that have no Update or DLC
  155. for (auto it = m_installedItems.begin(); it != m_installedItems.end();)
  156. {
  157. size_t titleType = (*it)->getTitleId() >> 32;
  158. if ((titleType != TitleKeys::SystemApplication && titleType != TitleKeys::SystemApplet) ||
  159. ((*it)->hasDLC() || (*it)->hasDLC()))
  160. it++;
  161. else
  162. it = m_installedItems.erase(it);
  163. }
  164. // For too long title name, shorten them
  165. for (auto& installedItem : m_installedItems)
  166. {
  167. installedItem->updateGameTitle();
  168. }
  169. // Copy the content of the installed list into the shown installed list
  170. m_installedItemsFiltered = m_installedItems;
  171. // Remove all titles that are not searched
  172. if (!m_currentSearch.empty()) {
  173. for (auto it = m_installedItemsFiltered.begin(); it != m_installedItemsFiltered.end();)
  174. {
  175. int matchScore;
  176. if (!fts::fuzzy_match(m_currentSearch.c_str(), (*it)->getAppItem()->getNormalizedTitle().c_str(), matchScore))
  177. it = m_installedItemsFiltered.erase(it);
  178. else
  179. it++;
  180. }
  181. }
  182. // Sort the list
  183. sort();
  184. m_isUpdatingList = false;
  185. }
  186. void InstalledList::draw(cpp3ds::RenderTarget &target, cpp3ds::RenderStates states) const
  187. {
  188. if (!m_canDrawList)
  189. return;
  190. states.transform *= getTransform();
  191. states.scissor = cpp3ds::UintRect(0, 50, 320, 190);
  192. for (auto& item : m_installedItemsFiltered)
  193. {
  194. float posY = item->getPosition().y;
  195. if (posY > -10.f && posY < 240.f)
  196. target.draw(*item, states);
  197. }
  198. if (m_expandedItem)
  199. target.draw(m_options, states);
  200. }
  201. void InstalledList::update(float delta)
  202. {
  203. #ifdef _3DS
  204. FS_CardType type;
  205. bool cardInserted;
  206. if (m_cardInserted != (R_SUCCEEDED(FSUSER_CardSlotIsInserted(&cardInserted)) && cardInserted && R_SUCCEEDED(FSUSER_GetCardType(&type)) && type == CARD_CTR))
  207. refresh();
  208. #endif
  209. m_tweenManager.update(delta);
  210. }
  211. bool InstalledList::processEvent(const cpp3ds::Event &event)
  212. {
  213. if (m_tweenManager.getRunningTweensCount() > 0)
  214. return false;
  215. if (event.type == cpp3ds::Event::TouchEnded)
  216. {
  217. if (event.touch.y < 30)
  218. return false;
  219. if (cpp3ds::UintRect(0, 32, 320, 16).contains(event.touch.x, event.touch.y)) {
  220. #ifndef EMULATION
  221. KeyboardApplet kb(KeyboardApplet::Text);
  222. swkbdSetHintText(kb, _("Type a game name...").toAnsiString().c_str());
  223. cpp3ds::String input = kb.getInput();
  224. filterBySearch(input);
  225. #else
  226. filterBySearch("");
  227. #endif
  228. return false;
  229. }
  230. for (auto &item : m_installedItemsFiltered)
  231. {
  232. float posY = getPosition().y + item->getPosition().y;
  233. if (event.touch.y > posY && event.touch.y < posY + item->getHeight())
  234. {
  235. if (item.get() == m_expandedItem)
  236. {
  237. if (event.touch.y < posY + 16.f)
  238. expandItem(nullptr);
  239. else
  240. m_options.processTouchEvent(event);
  241. }
  242. else
  243. expandItem(item.get());
  244. break;
  245. }
  246. }
  247. }
  248. return false;
  249. }
  250. void InstalledList::setScroll(float position)
  251. {
  252. m_scrollPos = std::round(position);
  253. repositionItems();
  254. }
  255. float InstalledList::getScroll()
  256. {
  257. return m_scrollPos;
  258. }
  259. void InstalledList::repositionItems()
  260. {
  261. float posY = 50.f + m_scrollPos;
  262. for (auto& item : m_installedItemsFiltered)
  263. {
  264. if (item.get() == m_expandedItem)
  265. m_options.setPosition(0.f, posY + 20.f);
  266. item->setPosition(0.f, posY);
  267. posY += item->getHeight();
  268. }
  269. m_size.y = posY - 6.f - m_scrollPos;
  270. if (m_expandedItem)
  271. m_size.y -= 24.f;
  272. updateScrollSize();
  273. }
  274. const cpp3ds::Vector2f &InstalledList::getScrollSize()
  275. {
  276. return m_size;
  277. }
  278. void InstalledList::expandItem(InstalledItem *item)
  279. {
  280. if (item == m_expandedItem)
  281. return;
  282. const float optionsFadeDelay = 0.15f;
  283. const float expandDuration = 0.2f;
  284. // Expand animation
  285. if (m_expandedItem)
  286. {
  287. TweenEngine::Tween::to(*m_expandedItem, InstalledItem::HEIGHT, expandDuration)
  288. .target(16.f)
  289. .setCallback(TweenEngine::TweenCallback::COMPLETE, [=](TweenEngine::BaseTween* source) {
  290. m_expandedItem = item;
  291. repositionItems();
  292. })
  293. .delay(optionsFadeDelay)
  294. .start(m_tweenManager);
  295. TweenEngine::Tween::to(m_options, InstalledOptions::ALPHA, optionsFadeDelay + 0.05f)
  296. .target(0.f)
  297. .start(m_tweenManager);
  298. }
  299. if (item)
  300. {
  301. TweenEngine::Tween::to(*item, InstalledItem::HEIGHT, expandDuration)
  302. .target(40.f)
  303. .setCallback(TweenEngine::TweenCallback::COMPLETE, [=](TweenEngine::BaseTween* source) {
  304. m_expandedItem = item;
  305. m_options.setInstalledItem(item);
  306. repositionItems();
  307. })
  308. .delay(m_expandedItem ? optionsFadeDelay : 0.f)
  309. .start(m_tweenManager);
  310. TweenEngine::Tween::to(m_options, InstalledOptions::ALPHA, expandDuration)
  311. .target(255.f)
  312. .delay(m_expandedItem ? expandDuration + optionsFadeDelay : optionsFadeDelay)
  313. .start(m_tweenManager);
  314. }
  315. // Move animation for items in between expanded items
  316. bool foundItem = false;
  317. bool foundExpanded = false;
  318. for (auto &itemToMove : m_installedItemsFiltered)
  319. {
  320. if (foundItem && !foundExpanded)
  321. {
  322. TweenEngine::Tween::to(*itemToMove, InstalledItem::POSITION_Y, expandDuration)
  323. .targetRelative(24.f)
  324. .delay(m_expandedItem ? optionsFadeDelay : 0.f)
  325. .start(m_tweenManager);
  326. }
  327. else if (foundExpanded && !foundItem)
  328. {
  329. TweenEngine::Tween::to(*itemToMove, InstalledItem::POSITION_Y, expandDuration)
  330. .targetRelative(-24.f)
  331. .delay(m_expandedItem ? optionsFadeDelay : 0.f)
  332. .start(m_tweenManager);
  333. }
  334. if (itemToMove.get() == m_expandedItem)
  335. foundExpanded = true;
  336. else if (itemToMove.get() == item)
  337. {
  338. foundItem = true;
  339. }
  340. if (foundExpanded && foundItem)
  341. break;
  342. }
  343. }
  344. bool InstalledList::isInstalled(cpp3ds::Uint64 titleId)
  345. {
  346. auto &v = getInstance().m_installedTitleIds;
  347. return std::find(v.begin(), v.end(), titleId) != v.end();
  348. }
  349. int InstalledList::getGameCount()
  350. {
  351. if (!m_isUpdatingList)
  352. m_gameCount = m_installedItems.size();
  353. return m_gameCount;
  354. }
  355. void InstalledList::sort()
  356. {
  357. m_tweenManager.killAll();
  358. std::sort(m_installedItemsFiltered.begin(), m_installedItemsFiltered.end(), [&](const std::shared_ptr<InstalledItem>& a, const std::shared_ptr<InstalledItem>& b)
  359. {
  360. if (m_sortAscending)
  361. {
  362. switch(m_sortType) {
  363. case Name: return a->getAppItem()->getNormalizedTitle() < b->getAppItem()->getNormalizedTitle();
  364. case Size: return a->getAppItem()->getFilesize() < b->getAppItem()->getFilesize();
  365. case VoteScore: return a->getAppItem()->getVoteScore() < b->getAppItem()->getVoteScore();
  366. case VoteCount: return a->getAppItem()->getVoteCount() < b->getAppItem()->getVoteCount();
  367. case ReleaseDate: return a->getAppItem()->getReleaseDate() < b->getAppItem()->getReleaseDate();
  368. }
  369. }
  370. else
  371. {
  372. switch(m_sortType) {
  373. case Name: return a->getAppItem()->getNormalizedTitle() > b->getAppItem()->getNormalizedTitle();
  374. case Size: return a->getAppItem()->getFilesize() > b->getAppItem()->getFilesize();
  375. case VoteScore: return a->getAppItem()->getVoteScore() > b->getAppItem()->getVoteScore();
  376. case VoteCount: return a->getAppItem()->getVoteCount() > b->getAppItem()->getVoteCount();
  377. case ReleaseDate: return a->getAppItem()->getReleaseDate() > b->getAppItem()->getReleaseDate();
  378. }
  379. }
  380. });
  381. repositionItems();
  382. }
  383. void InstalledList::setSortType(InstalledList::SortType sortType, bool ascending)
  384. {
  385. m_sortType = sortType;
  386. m_sortAscending = ascending;
  387. sort();
  388. }
  389. void InstalledList::filterBySearch(std::string search)
  390. {
  391. if (search != m_currentSearch) {
  392. m_currentSearch = search;
  393. g_browseState->setInstalledListSearchText(m_currentSearch);
  394. // Copy the content of the installed list into the shown installed list
  395. m_installedItemsFiltered = m_installedItems;
  396. // Remove all titles that are not searched
  397. if (!m_currentSearch.empty()) {
  398. for (auto it = m_installedItemsFiltered.begin(); it != m_installedItemsFiltered.end();)
  399. {
  400. int matchScore;
  401. if (!fts::fuzzy_match(m_currentSearch.c_str(), (*it)->getAppItem()->getNormalizedTitle().c_str(), matchScore))
  402. it = m_installedItemsFiltered.erase(it);
  403. else
  404. it++;
  405. }
  406. }
  407. sort();
  408. expandItem(nullptr);
  409. }
  410. }
  411. void InstalledList::setDrawList(bool canDrawList)
  412. {
  413. m_canDrawList = canDrawList;
  414. }
  415. bool InstalledList::canDrawList()
  416. {
  417. return m_canDrawList;
  418. }
  419. } // namespace FreeShop