AppList.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582
  1. #include <cpp3ds/System/FileSystem.hpp>
  2. #include <cpp3ds/System/FileInputStream.hpp>
  3. #include <functional>
  4. #include <iostream>
  5. #include <rapidjson/document.h>
  6. #include <rapidjson/filereadstream.h>
  7. #include <TweenEngine/Tween.h>
  8. #include <fstream>
  9. #include <cpp3ds/System/Clock.hpp>
  10. #include <cpp3ds/System/Sleep.hpp>
  11. #include "AppList.hpp"
  12. #include "Util.hpp"
  13. #include "TitleKeys.hpp"
  14. #include "AssetManager.hpp"
  15. namespace FreeShop {
  16. AppList::AppList(std::string jsonFilename)
  17. : m_sortType(Name)
  18. , m_selectedIndex(-1)
  19. , m_collapsed(false)
  20. , m_filterRegions(0)
  21. , m_filterLanguages(0)
  22. , m_sortAscending(true)
  23. , m_targetPosX(0.f)
  24. , m_indexDelta(0)
  25. , m_startKeyRepeat(false)
  26. , m_processedFirstKey(false)
  27. {
  28. m_jsonFilename = jsonFilename;
  29. m_soundBlip.setBuffer(AssetManager<cpp3ds::SoundBuffer>::get("sounds/blip.ogg"));
  30. }
  31. AppList::~AppList()
  32. {
  33. for (auto& texture : m_iconTextures)
  34. delete texture;
  35. }
  36. void AppList::refresh()
  37. {
  38. m_appItems.clear();
  39. m_guiAppItems.clear();
  40. #ifdef EMULATION
  41. bool isNew3DS = true;
  42. #else
  43. bool isNew3DS = false;
  44. APT_CheckNew3DS(&isNew3DS);
  45. #endif
  46. cpp3ds::Clock clock;
  47. cpp3ds::FileInputStream file;
  48. if (file.open(m_jsonFilename))
  49. {
  50. // Read file to string
  51. int size = file.getSize();
  52. std::string json;
  53. json.resize(size);
  54. file.read(&json[0], size);
  55. // Parse json string
  56. rapidjson::Document doc;
  57. doc.Parse(json.c_str());
  58. int i = 0;
  59. for (rapidjson::Value::ConstMemberIterator iter = doc.MemberBegin(); iter != doc.MemberEnd(); ++iter)
  60. {
  61. std::string id = iter->name.GetString();
  62. cpp3ds::Uint64 titleId = strtoull(id.c_str(), 0, 16);
  63. if (!isNew3DS && ((titleId >> 24) & 0xF) == 0xF)
  64. continue;
  65. if (TitleKeys::get(titleId))
  66. {
  67. auto appItem = std::make_shared<AppItem>();
  68. std::unique_ptr<GUI::AppItem> guiAppItem(new GUI::AppItem());
  69. appItem->loadFromJSON(iter->name.GetString(), iter->value);
  70. guiAppItem->setAppItem(appItem);
  71. // Move offscreen to avoid everything being drawn at once and crashing
  72. guiAppItem->setPosition(500.f, 100.f);
  73. m_appItems.emplace_back(std::move(appItem));
  74. m_guiAppItems.emplace_back(std::move(guiAppItem));
  75. }
  76. }
  77. }
  78. if (m_selectedIndex < 0 && getVisibleCount() > 0)
  79. m_selectedIndex = 0;
  80. sort();
  81. filter();
  82. reposition();
  83. setSelectedIndex(m_selectedIndex);
  84. }
  85. bool AppList::processEvent(const cpp3ds::Event &event)
  86. {
  87. if (event.type == cpp3ds::Event::KeyPressed)
  88. {
  89. m_processedFirstKey = false;
  90. if (event.key.code & cpp3ds::Keyboard::Up) {
  91. m_indexDelta = -1;
  92. } else if (event.key.code & cpp3ds::Keyboard::Down) {
  93. m_indexDelta = 1;
  94. } else if (event.key.code & cpp3ds::Keyboard::Left) {
  95. m_indexDelta = -4;
  96. } else if (event.key.code & cpp3ds::Keyboard::Right) {
  97. m_indexDelta = 4;
  98. } else if (event.key.code & cpp3ds::Keyboard::L) {
  99. m_indexDelta = -8;
  100. } else if (event.key.code & cpp3ds::Keyboard::R) {
  101. m_indexDelta = 8;
  102. } else
  103. m_processedFirstKey = true;
  104. }
  105. else if (event.type == cpp3ds::Event::KeyReleased)
  106. {
  107. if (event.key.code & (cpp3ds::Keyboard::Up | cpp3ds::Keyboard::Down | cpp3ds::Keyboard::Left | cpp3ds::Keyboard::Right | cpp3ds::Keyboard::L | cpp3ds::Keyboard::R))
  108. {
  109. if (cpp3ds::Keyboard::isKeyDown(cpp3ds::Keyboard::Up)
  110. || cpp3ds::Keyboard::isKeyDown(cpp3ds::Keyboard::Down)
  111. || cpp3ds::Keyboard::isKeyDown(cpp3ds::Keyboard::Left)
  112. || cpp3ds::Keyboard::isKeyDown(cpp3ds::Keyboard::Right)
  113. || cpp3ds::Keyboard::isKeyDown(cpp3ds::Keyboard::L)
  114. || cpp3ds::Keyboard::isKeyDown(cpp3ds::Keyboard::R))
  115. return false;
  116. m_indexDelta = 0;
  117. m_startKeyRepeat = false;
  118. m_processedFirstKey = false;
  119. }
  120. }
  121. return false;
  122. }
  123. void AppList::update(float delta)
  124. {
  125. if (m_indexDelta != 0)
  126. {
  127. if (m_startKeyRepeat)
  128. {
  129. if (m_clockKeyRepeat.getElapsedTime() > cpp3ds::milliseconds(60))
  130. processKeyRepeat();
  131. }
  132. else if (!m_processedFirstKey)
  133. {
  134. m_processedFirstKey = true;
  135. processKeyRepeat();
  136. }
  137. else if (m_clockKeyRepeat.getElapsedTime() > cpp3ds::milliseconds(300))
  138. m_startKeyRepeat = true;
  139. }
  140. m_tweenManager.update(delta);
  141. }
  142. void AppList::processKeyRepeat()
  143. {
  144. int index = getSelectedIndex();
  145. // Don't keep changing index on top/bottom boundaries
  146. if ((m_indexDelta != 1 || index % 4 != 3) && (m_indexDelta != -1 || index % 4 != 0))
  147. {
  148. m_soundBlip.play(1);
  149. setSelectedIndex(index + m_indexDelta);
  150. m_clockKeyRepeat.restart();
  151. }
  152. }
  153. void AppList::setSortType(AppList::SortType sortType, bool ascending)
  154. {
  155. m_sortType = sortType;
  156. m_sortAscending = ascending;
  157. sort();
  158. reposition();
  159. }
  160. void AppList::sort()
  161. {
  162. setSelectedIndex(-1);
  163. std::sort(m_guiAppItems.begin(), m_guiAppItems.end(), [&](const std::unique_ptr<GUI::AppItem>& a, const std::unique_ptr<GUI::AppItem>& b)
  164. {
  165. if (a->isFilteredOut() != b->isFilteredOut())
  166. return !a->isFilteredOut();
  167. if (a->getMatchScore() != b->getMatchScore())
  168. {
  169. return a->getMatchScore() > b->getMatchScore();
  170. }
  171. else
  172. {
  173. if (m_sortAscending)
  174. {
  175. switch(m_sortType) {
  176. case Name: return a->getAppItem()->getNormalizedTitle() < b->getAppItem()->getNormalizedTitle();
  177. case Size: return a->getAppItem()->getFilesize() < b->getAppItem()->getFilesize();
  178. case VoteScore: return a->getAppItem()->getVoteScore() < b->getAppItem()->getVoteScore();
  179. case VoteCount: return a->getAppItem()->getVoteCount() < b->getAppItem()->getVoteCount();
  180. case ReleaseDate: return a->getAppItem()->getReleaseDate() < b->getAppItem()->getReleaseDate();
  181. }
  182. }
  183. else
  184. {
  185. switch(m_sortType) {
  186. case Name: return a->getAppItem()->getNormalizedTitle() > b->getAppItem()->getNormalizedTitle();
  187. case Size: return a->getAppItem()->getFilesize() > b->getAppItem()->getFilesize();
  188. case VoteScore: return a->getAppItem()->getVoteScore() > b->getAppItem()->getVoteScore();
  189. case VoteCount: return a->getAppItem()->getVoteCount() > b->getAppItem()->getVoteCount();
  190. case ReleaseDate: return a->getAppItem()->getReleaseDate() > b->getAppItem()->getReleaseDate();
  191. }
  192. }
  193. }
  194. });
  195. if (!m_guiAppItems.empty())
  196. setSelectedIndex(0);
  197. }
  198. void AppList::filter()
  199. {
  200. m_tweenManager.killAll();
  201. // Region filter
  202. // Also resets the filter state when no region filter is set.
  203. if (m_filterRegions)
  204. {
  205. for (const auto& appItemGUI : m_guiAppItems)
  206. appItemGUI->setFilteredOut(!(appItemGUI->getAppItem()->getRegions() & m_filterRegions));
  207. }
  208. else
  209. for (const auto& appItemGUI : m_guiAppItems)
  210. appItemGUI->setFilteredOut(false);
  211. // Language filter
  212. if (m_filterLanguages != 0)
  213. {
  214. for (const auto& appItemGUI : m_guiAppItems)
  215. if (appItemGUI->isVisible())
  216. if (!(appItemGUI->getAppItem()->getLanguages() & m_filterLanguages))
  217. appItemGUI->setFilteredOut(true);
  218. }
  219. // Genre filter
  220. if (!m_filterGenres.empty())
  221. {
  222. for (const auto& appItemGUI : m_guiAppItems)
  223. if (appItemGUI->isVisible())
  224. {
  225. for (const auto& appGenre : appItemGUI->getAppItem()->getGenres())
  226. for (const auto& filterGenre : m_filterGenres)
  227. if (appGenre == filterGenre)
  228. goto matchedGenre;
  229. appItemGUI->setFilteredOut(true);
  230. matchedGenre:;
  231. }
  232. }
  233. // Platform filter
  234. if (!m_filterPlatforms.empty())
  235. {
  236. for (const auto& appItemGUI : m_guiAppItems)
  237. if (appItemGUI->isVisible())
  238. {
  239. for (const auto& filterPlatform : m_filterPlatforms)
  240. if (appItemGUI->getAppItem()->getPlatform() == filterPlatform)
  241. goto matchedPlatform;
  242. appItemGUI->setFilteredOut(true);
  243. matchedPlatform:;
  244. }
  245. }
  246. sort();
  247. reposition();
  248. setPosition(0.f, 0.f);
  249. }
  250. void AppList::reposition()
  251. {
  252. bool segmentFound = false;
  253. float destY = 4.f;
  254. int i = 0;
  255. float newX = m_selectedIndex / 4 * (m_collapsed ? 59.f : 200.f);
  256. for (auto& app : m_guiAppItems)
  257. {
  258. if (!app->isVisible() || app->isFilteredOut())
  259. continue;
  260. float destX = 3.f + (i/4) * (m_collapsed ? 59.f : 200.f);
  261. float itemPosX = app->getPosition().x + getPosition().x;
  262. if ((destX-newX < -200.f || destX-newX > 400.f) && (itemPosX < -200 || itemPosX > 400.f))
  263. {
  264. if (segmentFound == m_collapsed)
  265. TweenEngine::Tween::set(*app, GUI::AppItem::POSITION_XY)
  266. .target(destX, destY)
  267. .delay(0.3f)
  268. .start(m_tweenManager);
  269. else
  270. app->setPosition(destX, destY);
  271. }
  272. else
  273. {
  274. segmentFound = true;
  275. TweenEngine::Tween::to(*app, GUI::AppItem::POSITION_XY, 0.3f)
  276. .target(destX, destY)
  277. .start(m_tweenManager);
  278. }
  279. if (++i % 4 == 0)
  280. destY = 4.f;
  281. else
  282. destY += 59.f;
  283. }
  284. }
  285. void AppList::setSelectedIndex(int index)
  286. {
  287. if (getVisibleCount() == 0)
  288. return;
  289. if (index < 0)
  290. index = 0;
  291. else if (index >= getVisibleCount())
  292. index = getVisibleCount() - 1;
  293. float extra = 1.0f; //std::abs(m_appList.getSelectedIndex() - index) == 8.f ? 2.f : 1.f;
  294. float pos = -200.f * extra * (index / 4);
  295. if (pos > m_targetPosX)
  296. m_targetPosX = pos;
  297. else if (pos <= m_targetPosX - 400.f)
  298. m_targetPosX = pos + 200.f * extra;
  299. TweenEngine::Tween::to(*this, AppList::POSITION_X, 0.3f)
  300. .target(m_targetPosX)
  301. .start(m_tweenManager);
  302. if (m_selectedIndex >= 0)
  303. m_guiAppItems[m_selectedIndex]->deselect();
  304. m_selectedIndex = index;
  305. if (m_selectedIndex >= 0)
  306. m_guiAppItems[m_selectedIndex]->select();
  307. }
  308. int AppList::getSelectedIndex() const
  309. {
  310. return m_selectedIndex;
  311. }
  312. GUI::AppItem *AppList::getSelected()
  313. {
  314. if (m_selectedIndex < 0 || m_selectedIndex > m_guiAppItems.size()-1)
  315. return nullptr;
  316. return m_guiAppItems[m_selectedIndex].get();
  317. }
  318. void AppList::draw(cpp3ds::RenderTarget &target, cpp3ds::RenderStates states) const
  319. {
  320. states.transform *= getTransform();
  321. for (auto& app : m_guiAppItems)
  322. {
  323. if (app->isVisible() && !app->isFilteredOut())
  324. target.draw(*app, states);
  325. }
  326. }
  327. size_t AppList::getCount() const
  328. {
  329. return m_guiAppItems.size();
  330. }
  331. size_t AppList::getVisibleCount() const
  332. {
  333. size_t count = 0;
  334. for (auto& item : m_guiAppItems)
  335. if (item->isVisible() && !item->isFilteredOut())
  336. count++;
  337. else break;
  338. return count;
  339. }
  340. void AppList::setCollapsed(bool collapsed)
  341. {
  342. if (m_collapsed == collapsed)
  343. return;
  344. m_tweenManager.killAll();
  345. float newX = m_selectedIndex / 4 * (collapsed ? 59.f : 200.f);
  346. if (collapsed)
  347. {
  348. for (auto &app : m_guiAppItems)
  349. {
  350. float appPosX = app->getPosition().x + getPosition().x;
  351. if (appPosX >= 0.f && appPosX < 400.f)
  352. TweenEngine::Tween::to(*app, GUI::AppItem::INFO_ALPHA, 0.3f)
  353. .target(0)
  354. .setCallback(TweenEngine::TweenCallback::COMPLETE, [&](TweenEngine::BaseTween *source) {
  355. app->setInfoVisible(false);
  356. })
  357. .start(m_tweenManager);
  358. else {
  359. app->setInfoVisible(false);
  360. TweenEngine::Tween::set(*app, GUI::AppItem::INFO_ALPHA)
  361. .target(0)
  362. .start(m_tweenManager);
  363. }
  364. }
  365. TweenEngine::Tween::to(*this, AppList::POSITION_X, 0.3f)
  366. .target(-newX)
  367. .delay(0.3f)
  368. .setCallback(TweenEngine::TweenCallback::START, [=](TweenEngine::BaseTween *source) {
  369. m_collapsed = collapsed;
  370. reposition();
  371. })
  372. .start(m_tweenManager);
  373. }
  374. else
  375. {
  376. m_collapsed = collapsed;
  377. reposition();
  378. TweenEngine::Tween::to(*this, AppList::POSITION_X, 0.3f)
  379. .target(-newX)
  380. .setCallback(TweenEngine::TweenCallback::COMPLETE, [this, newX](TweenEngine::BaseTween *source) {
  381. for (auto &app : m_guiAppItems) {
  382. float appPosX = app->getPosition().x - newX;
  383. app->setInfoVisible(true);
  384. if (appPosX >= 0.f && appPosX < 400.f)
  385. TweenEngine::Tween::to(*app, GUI::AppItem::INFO_ALPHA, 0.3f)
  386. .target(255.f)
  387. .start(m_tweenManager);
  388. else
  389. TweenEngine::Tween::set(*app, GUI::AppItem::INFO_ALPHA)
  390. .target(255.f)
  391. .start(m_tweenManager);
  392. }
  393. GUI::AppItem *item = getSelected();
  394. if (item)
  395. TweenEngine::Tween::to(*item, GUI::AppItem::BACKGROUND_ALPHA, 0.3f)
  396. .target(255.f)
  397. .setCallback(TweenEngine::TweenCallback::COMPLETE, [this](TweenEngine::BaseTween *source) {
  398. setSelectedIndex(m_selectedIndex);
  399. })
  400. .delay(0.3f)
  401. .start(m_tweenManager);
  402. })
  403. .start(m_tweenManager);
  404. }
  405. }
  406. bool AppList::isCollapsed() const
  407. {
  408. return m_collapsed;
  409. }
  410. void AppList::filterBySearch(const std::string &searchTerm, std::vector<util3ds::RichText> &textMatches)
  411. {
  412. m_tweenManager.killAll();
  413. for (auto& item : m_guiAppItems)
  414. {
  415. item->setMatchTerm(searchTerm);
  416. if (item->getMatchScore() > -99)
  417. {
  418. item->setVisible(true);
  419. TweenEngine::Tween::to(*item, SCALE_XY, 0.3)
  420. .target(1.f, 1.f)
  421. .start(m_tweenManager);
  422. }
  423. else{
  424. GUI::AppItem* itemptr = item.get();
  425. TweenEngine::Tween::to(*item, SCALE_XY, 0.3)
  426. .target(0.f, 0.f)
  427. .setCallback(TweenEngine::TweenCallback::COMPLETE, [itemptr](TweenEngine::BaseTween *source) {
  428. itemptr->setVisible(false);
  429. })
  430. .start(m_tweenManager);
  431. }
  432. }
  433. if (m_selectedIndex >= 0)
  434. m_guiAppItems[m_selectedIndex]->deselect();
  435. sort();
  436. reposition();
  437. int i = 0;
  438. for (auto& textMatch : textMatches)
  439. {
  440. textMatch.clear();
  441. if (searchTerm.empty())
  442. continue;
  443. if (i < getVisibleCount())
  444. {
  445. auto item = m_guiAppItems[i].get();
  446. if (item->getMatchScore() > -99)
  447. {
  448. bool matching = false;
  449. const char *str = item->getAppItem()->getNormalizedTitle().c_str();
  450. const char *pattern = searchTerm.c_str();
  451. const char *strLastPos = str;
  452. auto title = item->getAppItem()->getTitle().toUtf8();
  453. auto titleCurPos = title.begin();
  454. auto titleLastPos = title.begin();
  455. textMatch << cpp3ds::Color(150,150,150);
  456. while (*str != '\0') {
  457. if (tolower(*pattern) == tolower(*str)) {
  458. if (!matching) {
  459. matching = true;
  460. if (str != strLastPos) {
  461. textMatch << cpp3ds::String::fromUtf8(titleLastPos, titleCurPos);
  462. titleLastPos = titleCurPos;
  463. strLastPos = str;
  464. }
  465. textMatch << cpp3ds::Color::Black;
  466. }
  467. ++pattern;
  468. } else {
  469. if (matching) {
  470. matching = false;
  471. if (str != strLastPos) {
  472. textMatch << cpp3ds::String::fromUtf8(titleLastPos, titleCurPos);
  473. titleLastPos = titleCurPos;
  474. strLastPos = str;
  475. }
  476. textMatch << cpp3ds::Color(150,150,150);
  477. }
  478. }
  479. ++str;
  480. titleCurPos = cpp3ds::Utf8::next(titleCurPos, title.end());
  481. }
  482. if (str != strLastPos)
  483. textMatch << cpp3ds::String::fromUtf8(titleLastPos, title.end());
  484. }
  485. i++;
  486. }
  487. }
  488. if (m_guiAppItems.size() > 0 && textMatches.size() > 0)
  489. setSelectedIndex(0);
  490. }
  491. std::vector<std::unique_ptr<GUI::AppItem>> &AppList::getList()
  492. {
  493. return m_guiAppItems;
  494. }
  495. AppList &AppList::getInstance()
  496. {
  497. static AppList list(FREESHOP_DIR "/cache/data.json");
  498. return list;
  499. }
  500. void AppList::setFilterGenres(const std::vector<int> &genres)
  501. {
  502. m_filterGenres = genres;
  503. filter();
  504. }
  505. void AppList::setFilterPlatforms(const std::vector<int> &platforms)
  506. {
  507. m_filterPlatforms = platforms;
  508. filter();
  509. }
  510. void AppList::setFilterRegions(int regions)
  511. {
  512. m_filterRegions = regions;
  513. filter();
  514. }
  515. void AppList::setFilterLanguages(int languages)
  516. {
  517. m_filterLanguages = languages;
  518. filter();
  519. }
  520. } // namespace FreeShop