AppInfo.cpp 40 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258
  1. #include <cpp3ds/System/Err.hpp>
  2. #include <cpp3ds/Window/Window.hpp>
  3. #include <iostream>
  4. #include <cpp3ds/System/I18n.hpp>
  5. #include "AssetManager.hpp"
  6. #include "AppInfo.hpp"
  7. #include "Download.hpp"
  8. #include "Util.hpp"
  9. #include "DownloadQueue.hpp"
  10. #include "Notification.hpp"
  11. #include "InstalledList.hpp"
  12. #include "FreeShop.hpp"
  13. #include "Theme.hpp"
  14. #include "Config.hpp"
  15. #include "LoadInformations.hpp"
  16. #include "States/DialogState.hpp"
  17. #include <sstream>
  18. #include <TweenEngine/Tween.h>
  19. #include <cpp3ds/System/FileInputStream.hpp>
  20. #include <cpp3ds/System/FileSystem.hpp>
  21. #include <sys/stat.h>
  22. #include <cmath>
  23. namespace FreeShop
  24. {
  25. AppInfo::AppInfo()
  26. : m_appItem(nullptr)
  27. , m_currentScreenshot(0)
  28. , m_descriptionVelocity(0.f)
  29. , m_isDemoInstalled(false)
  30. , m_isBannerLoaded(false)
  31. , m_showAppStats(false)
  32. , m_threadUninstallGame(&AppInfo::uninstallGame, this)
  33. , m_threadUninstallDemo(&AppInfo::uninstallDemo, this)
  34. , m_canDraw(true)
  35. {
  36. m_textDownload.setFillColor(cpp3ds::Color::White);
  37. m_textDownload.setOutlineColor(cpp3ds::Color(0, 0, 0, 200));
  38. m_textDownload.setOutlineThickness(2.f);
  39. m_textDownload.setFont(AssetManager<cpp3ds::Font>::get("fonts/fontawesome.ttf"));
  40. m_textDownload.setString("\uf019");
  41. m_textDownload.setCharacterSize(30);
  42. m_textDownload.setPosition(285.f, 200.f);
  43. m_textSleepDownload = m_textDownload;
  44. m_textSleepDownload.setFillColor(cpp3ds::Color::White);
  45. m_textSleepDownload.setString("\uf186");
  46. m_textSleepDownload.setPosition(192.f, 198.f);
  47. m_textDelete = m_textDownload;
  48. m_textDelete.setString("\uf1f8");
  49. m_textDelete.setPosition(285.f, 200.f);
  50. m_textExecute = m_textDownload;
  51. m_textExecute.setString("\uf01d");
  52. m_textExecute.setPosition(192.f, 200.f);
  53. m_arrowLeft.setFont(AssetManager<cpp3ds::Font>::get("fonts/fontawesome.ttf"));
  54. m_arrowLeft.setCharacterSize(24);
  55. m_arrowLeft.setFillColor(cpp3ds::Color(255, 255, 255, 150));
  56. m_arrowLeft.setOutlineColor(cpp3ds::Color(0, 0, 0, 100));
  57. m_arrowLeft.setOutlineThickness(1.f);
  58. m_arrowLeft.setPosition(4.f, 100.f);
  59. m_arrowLeft.setString("\uf053");
  60. m_arrowRight = m_arrowLeft;
  61. m_arrowRight.setPosition(298.f, 100.f);
  62. m_arrowRight.setString("\uf054");
  63. m_close = m_arrowLeft;
  64. m_close.setCharacterSize(30);
  65. m_close.setPosition(285.f, 4.f);
  66. m_close.setString("\uf00d");
  67. if (Theme::isTextThemed)
  68. m_screenshotsBackground.setOutlineColor(Theme::boxOutlineColor);
  69. else
  70. m_screenshotsBackground.setOutlineColor(cpp3ds::Color(158, 158, 158, 255));
  71. m_screenshotsBackground.setOutlineThickness(1);
  72. if (Theme::isTextThemed)
  73. m_screenshotsBackground.setFillColor(Theme::boxColor);
  74. else
  75. m_screenshotsBackground.setFillColor(cpp3ds::Color(245, 245, 245));
  76. m_screenshotsBackground.setSize(cpp3ds::Vector2f(320.f, 74.f));
  77. m_screenshotsBackground.setPosition(0.f, 166.f);
  78. if (Theme::isTextThemed)
  79. m_separator.setFillColor(Theme::boxOutlineColor);
  80. else
  81. m_separator.setFillColor(cpp3ds::Color(158, 158, 158, 255));
  82. m_separator.setSize(cpp3ds::Vector2f(1.f, 74.f));
  83. m_separator.setPosition(184.f, 166.f);
  84. m_textScreenshotsEmpty.setCharacterSize(12);
  85. if (Theme::isTextThemed)
  86. m_textScreenshotsEmpty.setFillColor(Theme::secondaryTextColor);
  87. else
  88. m_textScreenshotsEmpty.setFillColor(cpp3ds::Color(100, 100, 100, 255));
  89. m_textScreenshotsEmpty.useSystemFont();
  90. m_textScreenshotsEmpty.setString(_("No Screenshots"));
  91. cpp3ds::FloatRect textRect = m_textScreenshotsEmpty.getLocalBounds();
  92. m_textScreenshotsEmpty.setOrigin(m_textScreenshotsEmpty.getLocalBounds().width / 2, m_textScreenshotsEmpty.getLocalBounds().height / 2);
  93. m_textScreenshotsEmpty.setPosition(92.f, 199.f);
  94. m_textNothingSelected.setString(_("Select a game to start"));
  95. m_textNothingSelected.setCharacterSize(16);
  96. if (Theme::isTextThemed)
  97. m_textNothingSelected.setFillColor(Theme::secondaryTextColor);
  98. else
  99. m_textNothingSelected.setFillColor(cpp3ds::Color(0, 0, 0, 120));
  100. m_textNothingSelected.useSystemFont();
  101. m_textNothingSelected.setPosition(160.f, 105.f);
  102. m_textNothingSelected.setOrigin(m_textNothingSelected.getLocalBounds().width / 2, m_textNothingSelected.getLocalBounds().height / 2);
  103. TweenEngine::Tween::to(m_textNothingSelected, m_textNothingSelected.SCALE_XY, 4.f)
  104. .target(0.9f, 0.9f).repeatYoyo(-1, 0.f).start(m_tweenManager);
  105. m_textTitle.setCharacterSize(15);
  106. if (Theme::isTextThemed)
  107. m_textTitle.setFillColor(Theme::primaryTextColor);
  108. else
  109. m_textTitle.setFillColor(cpp3ds::Color::Black);
  110. m_textTitle.setPosition(28.f, 27.f);
  111. m_textTitle.useSystemFont();
  112. m_textDescription.setCharacterSize(12);
  113. m_textDescriptionDrawn.setCharacterSize(12);
  114. if (Theme::isTextThemed)
  115. m_textDescriptionDrawn << Theme::secondaryTextColor;
  116. else
  117. m_textDescriptionDrawn << cpp3ds::Color(100, 100, 100, 255);
  118. m_textDescription.useSystemFont();
  119. m_textDescriptionDrawn.useSystemFont();
  120. m_textLittleDescription.setCharacterSize(10);
  121. if (Theme::isTextThemed)
  122. m_textLittleDescription.setFillColor(Theme::secondaryTextColor);
  123. else
  124. m_textLittleDescription.setFillColor(cpp3ds::Color(100, 100, 100, 255));
  125. m_textLittleDescription.useSystemFont();
  126. m_textLittleDescription.setPosition(28.f, 44.f);
  127. m_textTitleId.setCharacterSize(11);
  128. if (Theme::isTextThemed)
  129. m_textTitleId.setFillColor(Theme::secondaryTextColor);
  130. else
  131. m_textTitleId.setFillColor(cpp3ds::Color(80, 80, 80, 255));
  132. m_textTitleId.setPosition(192.f, 185.f);
  133. m_textDemo.setString(_("Demo"));
  134. if (Theme::isTextThemed)
  135. m_textDemo.setFillColor(Theme::secondaryTextColor);
  136. else
  137. m_textDemo.setFillColor(cpp3ds::Color(100, 100, 100));
  138. m_textDemo.setCharacterSize(10);
  139. m_textDemo.setPosition(214.f, 171.f);
  140. m_textDemo.useSystemFont();
  141. m_textIconDemo.setFont(AssetManager<cpp3ds::Font>::get("fonts/fontawesome.ttf"));
  142. m_textIconDemo.setFillColor(cpp3ds::Color(50, 100, 50));
  143. m_textIconDemo.setCharacterSize(18);
  144. m_textIconDemo.setPosition(192.f, 166.f);
  145. if (fopen(FREESHOP_DIR "/theme/images/fade.png", "rb"))
  146. m_fadeTextRect.setTexture(&AssetManager<cpp3ds::Texture>::get(FREESHOP_DIR "/theme/images/fade.png"));
  147. else
  148. m_fadeTextRect.setTexture(&AssetManager<cpp3ds::Texture>::get("images/fade.png"));
  149. m_fadeTextRect.setSize(cpp3ds::Vector2f(250.f, 8.f));
  150. m_fadeTextRect.setOrigin(m_fadeTextRect.getSize());
  151. m_fadeTextRect.setRotation(180.f);
  152. m_fadeTextRect.setPosition(102.f, 46.f);
  153. m_icon.setPosition(2.f, 30.f);
  154. m_icon.setScale(.5f, .5f);
  155. m_fadeRect.setPosition(0.f, 30.f);
  156. m_fadeRect.setSize(cpp3ds::Vector2f(320.f, 210.f));
  157. m_fadeRect.setFillColor(cpp3ds::Color::White);
  158. m_overlay.setSize(cpp3ds::Vector2f(400.f, 240.f));
  159. m_overlay.setFillColor(cpp3ds::Color::Transparent);
  160. m_topIcon.setPosition(5.f, 36.f);
  161. }
  162. AppInfo::~AppInfo()
  163. {
  164. }
  165. AppInfo &AppInfo::getInstance()
  166. {
  167. static AppInfo appInfo;
  168. return appInfo;
  169. }
  170. void AppInfo::drawTop(cpp3ds::Window &window)
  171. {
  172. if (m_currentScreenshot)
  173. window.draw(m_screenshotTopTop);
  174. if (m_isBannerLoaded) {
  175. window.draw(m_overlay);
  176. window.draw(m_gameBanner);
  177. window.draw(m_topIcon);
  178. }
  179. m_appStats.drawTop(window);
  180. }
  181. void AppInfo::draw(cpp3ds::RenderTarget &target, cpp3ds::RenderStates states) const
  182. {
  183. if (!m_canDraw)
  184. return;
  185. states.transform *= getTransform();
  186. if (m_appItem)
  187. {
  188. target.draw(m_screenshotsBackground, states);
  189. target.draw(m_separator, states);
  190. if (m_screenshotTops.empty())
  191. target.draw(m_textScreenshotsEmpty, states);
  192. target.draw(m_icon, states);
  193. target.draw(m_textTitle, states);
  194. target.draw(m_textLittleDescription, states);
  195. if (Config::get(Config::TitleID).GetBool())
  196. target.draw(m_textTitleId, states);
  197. states.scissor = cpp3ds::UintRect(0, 55, 320, 109);
  198. target.draw(m_textDescriptionDrawn, states);
  199. target.draw(m_scrollbar);
  200. states.scissor = cpp3ds::UintRect();
  201. if (m_appItem->isInstalled())
  202. {
  203. target.draw(m_textExecute, states);
  204. target.draw(m_textDelete, states);
  205. }
  206. else if (!DownloadQueue::getInstance().isDownloading(m_appItem))
  207. {
  208. target.draw(m_textDownload, states);
  209. target.draw(m_textSleepDownload, states);
  210. }
  211. if (!m_appItem->getDemos().empty() && !DownloadQueue::getInstance().isDownloading(m_appItem->getDemos()[0]))
  212. {
  213. target.draw(m_textDemo, states);
  214. target.draw(m_textIconDemo, states);
  215. }
  216. int i = 1;
  217. for (auto& screenshot : m_screenshotTops) {
  218. if (m_currentScreenshot != i)
  219. target.draw(*screenshot, states);
  220. i++;
  221. }
  222. i = 1;
  223. for (auto& screenshot : m_screenshotBottoms) {
  224. if (m_currentScreenshot != i)
  225. target.draw(*screenshot, states);
  226. i++;
  227. }
  228. target.draw(m_fadeRect, states);
  229. target.draw(m_appStats, states);
  230. }
  231. else
  232. {
  233. target.draw(m_textNothingSelected, states);
  234. }
  235. if (m_currentScreenshot)
  236. {
  237. target.draw(m_screenshotTopBottom);
  238. target.draw(m_screenshotBottom);
  239. if (m_currentScreenshot > 1)
  240. target.draw(m_arrowLeft);
  241. if (m_currentScreenshot < m_screenshotTops.size())
  242. target.draw(m_arrowRight);
  243. target.draw(m_close);
  244. }
  245. }
  246. void AppInfo::setValues(int tweenType, float *newValues)
  247. {
  248. switch (tweenType) {
  249. case ALPHA: {
  250. cpp3ds::Color color = m_fadeRect.getFillColor();
  251. color.a = 255.f - std::max(std::min(newValues[0], 255.f), 0.f);
  252. m_fadeRect.setFillColor(color);
  253. break;
  254. }
  255. default:
  256. TweenTransformable::setValues(tweenType, newValues);
  257. }
  258. }
  259. int AppInfo::getValues(int tweenType, float *returnValues)
  260. {
  261. switch (tweenType) {
  262. case ALPHA:
  263. returnValues[0] = 255.f - m_fadeRect.getFillColor().a;
  264. return 1;
  265. default:
  266. return TweenTransformable::getValues(tweenType, returnValues);
  267. }
  268. }
  269. void AppInfo::loadApp(std::shared_ptr<AppItem> appItem)
  270. {
  271. if (m_appItem == appItem)
  272. return;
  273. m_appItem = appItem;
  274. m_appStats.setVisible(false);
  275. if (appItem)
  276. {
  277. std::string countryCode = getCountryCode(appItem->getRegions());
  278. std::string jsonFilename = appItem->getJsonFilename();
  279. std::string urlTitleData = "https://samurai.ctr.shop.nintendo.net/samurai/ws/" + countryCode + "/title/" + appItem->getContentId() + "/?shop_id=1";
  280. std::string dir = cpp3ds::FileSystem::getFilePath(FREESHOP_DIR "/tmp/" + appItem->getTitleIdStr());
  281. if (!pathExists(dir.c_str(), false))
  282. mkdir(dir.c_str(), 0777);
  283. if (!appItem->isCached())
  284. {
  285. Download download(urlTitleData, jsonFilename);
  286. download.setField("Accept", "application/json");
  287. download.run();
  288. auto status = download.getLastResponse().getStatus();
  289. if (status != cpp3ds::Http::Response::Ok && status != cpp3ds::Http::Response::PartialContent)
  290. {
  291. urlTitleData = "https://samurai.ctr.shop.nintendo.net/samurai/ws/" + appItem->getUriRegion() + "/title/" + appItem->getContentId() + "/?shop_id=1";
  292. download.setUrl(urlTitleData);
  293. download.run();
  294. }
  295. }
  296. updateInfo();
  297. m_textTitle.setString("");
  298. m_textLittleDescription.setString("");
  299. m_textDescriptionDrawn.clear();
  300. m_textTitleId.setString(appItem->getTitleIdStr());
  301. // Transform the game's release date to human readable time
  302. time_t t = m_appItem->getReleaseDate();
  303. struct tm * timeinfo;
  304. timeinfo = localtime(&t);
  305. char timeTextFmt[12];
  306. strftime(timeTextFmt, 12, "%d/%m/%Y", timeinfo);
  307. // Transform the game's size to human readable size
  308. cpp3ds::String tempSizeHolder;
  309. if (m_appItem->getFilesize() > 1024 * 1024 * 1024)
  310. tempSizeHolder = _("%.1f GB", static_cast<float>(m_appItem->getFilesize()) / 1024.f / 1024.f / 1024.f);
  311. else if (m_appItem->getFilesize() > 1024 * 1024)
  312. tempSizeHolder = _("%.1f MB", static_cast<float>(m_appItem->getFilesize()) / 1024.f / 1024.f);
  313. else
  314. tempSizeHolder = _("%d KB", m_appItem->getFilesize() / 1024);
  315. cpp3ds::IntRect textureRect;
  316. m_icon.setTexture(*appItem->getIcon(textureRect), true);
  317. m_icon.setTextureRect(textureRect);
  318. m_screenshotTops.clear();
  319. m_screenshotTopTextures.clear();
  320. m_screenshotBottoms.clear();
  321. m_screenshotBottomTextures.clear();
  322. rapidjson::Document doc;
  323. std::string json;
  324. cpp3ds::FileInputStream file;
  325. if (file.open(jsonFilename))
  326. {
  327. json.resize(file.getSize());
  328. file.read(&json[0], json.size());
  329. doc.Parse(json.c_str());
  330. if (!doc.HasParseError())
  331. {
  332. if (doc["title"].HasMember("banner_url"))
  333. setBanner(doc["title"]["banner_url"]);
  334. if (doc["title"].HasMember("screenshots"))
  335. setScreenshots(doc["title"]["screenshots"]["screenshot"]);
  336. if (doc["title"].HasMember("description"))
  337. setDescription(doc["title"]["description"]);
  338. m_textDescription.setPosition(2.f, 55.f);
  339. m_textDescriptionDrawn.setPosition(2.f, 55.f);
  340. if (doc["title"].HasMember("star_rating_info"))
  341. m_appStats.loadApp(appItem, doc["title"]["star_rating_info"]);
  342. if (doc["title"].HasMember("preference"))
  343. m_appStats.loadPreferences(doc["title"]["preference"]);
  344. m_textTitle.setString(appItem->getTitle());
  345. if (doc["title"].HasMember("copyright")) {
  346. cpp3ds::String copyright = doc["title"]["copyright"]["text"].GetString();
  347. copyright.replace("<br>", "");
  348. copyright.replace("<BR>", "");
  349. copyright.replace("<br/>", "");
  350. m_textDescriptionDrawn << "\n\n";
  351. m_textDescriptionDrawn << calculateWordWrapping(copyright);
  352. }
  353. if (Config::get(Config::ShowGameDescription).GetBool())
  354. addInfoToDescription(doc["title"]);
  355. // Set little description text
  356. cpp3ds::String textLittleDescription;
  357. if (doc["title"].HasMember("new") && doc["title"]["new"].GetBool())
  358. textLittleDescription.insert(textLittleDescription.getSize(), _("NEW - "));
  359. textLittleDescription.insert(textLittleDescription.getSize(), _("%s - %s", tempSizeHolder.toAnsiString().c_str(), timeTextFmt));
  360. if (doc["title"].HasMember("rating_info")) {
  361. if (doc["title"]["rating_info"].HasMember("rating_system") && doc["title"]["rating_info"].HasMember("rating")) {
  362. if (doc["title"]["rating_info"]["rating_system"].HasMember("name") && doc["title"]["rating_info"]["rating"].HasMember("name")) {
  363. textLittleDescription.insert(textLittleDescription.getSize(), _(" - %s %s", doc["title"]["rating_info"]["rating_system"]["name"].GetString(), doc["title"]["rating_info"]["rating"]["name"].GetString()));
  364. }
  365. }
  366. }
  367. m_textLittleDescription.setString(_("%s", textLittleDescription.toAnsiString().c_str()));
  368. // Shorten the app name if it's out of the screen
  369. int maxSize = 290;
  370. if (m_textTitle.getLocalBounds().width > maxSize) {
  371. cpp3ds::Text tmpText = m_textTitle;
  372. tmpText.setString("");
  373. auto s = m_textTitle.getString().toUtf8();
  374. for (int i = 0; i <= s.size(); ++i) {
  375. tmpText.setString(cpp3ds::String::fromUtf8(s.begin(), s.begin() + i));
  376. if (tmpText.getLocalBounds().width > maxSize) {
  377. cpp3ds::String titleTxt = tmpText.getString();
  378. titleTxt.erase(titleTxt.getSize() - 3, 3);
  379. titleTxt.insert(titleTxt.getSize(), "...");
  380. m_textTitle.setString(titleTxt);
  381. break;
  382. }
  383. }
  384. }
  385. m_textDownload.setFillColor(cpp3ds::Color::White);
  386. m_textSleepDownload.setFillColor(cpp3ds::Color::White);
  387. m_scrollbar.setSize(cpp3ds::Vector2u(2, 109));
  388. m_scrollbar.setScrollAreaSize(cpp3ds::Vector2u(320, 109));
  389. m_scrollbar.setDragRect(cpp3ds::IntRect(0, 20, 320, 146));
  390. m_scrollbar.setColor(cpp3ds::Color(0, 0, 0, 128));
  391. m_scrollbar.setPosition(312.f, 56.f);
  392. m_scrollbar.setAutoHide(false);
  393. m_scrollbar.attachObject(this);
  394. m_scrollbar.setScroll(0.f);
  395. m_scrollbar.markDirty();
  396. m_scrollSize = cpp3ds::Vector2f(320.f, m_textDescriptionDrawn.getLocalBounds().top + m_textDescriptionDrawn.getLocalBounds().height + 5.f);
  397. closeBanner();
  398. }
  399. }
  400. }
  401. }
  402. const std::shared_ptr<AppItem> AppInfo::getAppItem() const
  403. {
  404. return m_appItem;
  405. }
  406. #define SCREENSHOT_BUTTON_FADEIN(object) \
  407. object.setFillColor(cpp3ds::Color(255,255,255,190)); \
  408. object.setOutlineColor(cpp3ds::Color(0,0,0,100)); \
  409. TweenEngine::Tween::from(object, util3ds::TweenText::FILL_COLOR_ALPHA, 4.f) \
  410. .target(0).start(m_tweenManager); \
  411. TweenEngine::Tween::from(object, util3ds::TweenText::OUTLINE_COLOR_ALPHA, 4.f) \
  412. .target(0).start(m_tweenManager);
  413. #define SCREENSHOT_BUTTON_FADEOUT(object) \
  414. TweenEngine::Tween::to(object, util3ds::TweenText::FILL_COLOR_ALPHA, 0.5f) \
  415. .target(0).start(m_tweenManager); \
  416. TweenEngine::Tween::to(object, util3ds::TweenText::OUTLINE_COLOR_ALPHA, 0.5f) \
  417. .target(0).start(m_tweenManager);
  418. void AppInfo::setCurrentScreenshot(int screenshotIndex)
  419. {
  420. if (screenshotIndex != 0)
  421. {
  422. cpp3ds::Sprite* screenshotTop = m_screenshotTops[screenshotIndex-1].get();
  423. cpp3ds::Sprite* screenshotBottom = m_screenshotBottoms[screenshotIndex-1].get();
  424. m_screenshotTopTop.setTexture(*screenshotTop->getTexture(), true);
  425. m_screenshotTopBottom.setTexture(*screenshotTop->getTexture(), true);
  426. m_screenshotBottom.setTexture(*screenshotBottom->getTexture(), true);
  427. // Calculate scale for screenshots that are not 400x240 (top screen) / 320x240 (bottom screen)
  428. float scaleTopX = 400.f / m_screenshotTopBottom.getTexture()->getSize().x;
  429. float scaleBotX = 320.f / m_screenshotBottom.getTexture()->getSize().x;
  430. float scaleY = 240.f / m_screenshotTopBottom.getTexture()->getSize().y;
  431. // If another screenshot not already showing
  432. if (m_currentScreenshot == 0)
  433. {
  434. SCREENSHOT_BUTTON_FADEIN(m_close);
  435. SCREENSHOT_BUTTON_FADEIN(m_arrowLeft);
  436. SCREENSHOT_BUTTON_FADEIN(m_arrowRight);
  437. m_screenshotTopTop.setPosition(screenshotTop->getPosition() + cpp3ds::Vector2f(0.f, 270.f));
  438. m_screenshotTopTop.setScale(screenshotTop->getScale());
  439. m_screenshotTopBottom.setPosition(screenshotTop->getPosition());
  440. m_screenshotTopBottom.setScale(screenshotTop->getScale());
  441. m_screenshotBottom.setPosition(screenshotBottom->getPosition());
  442. m_screenshotBottom.setScale(screenshotBottom->getScale());
  443. TweenEngine::Tween::to(m_screenshotTopBottom, util3ds::TweenSprite::SCALE_XY, 0.5f)
  444. .target(0.18f * scaleTopX / 1.f, 0.18f * scaleY / 1.f)
  445. .ease(TweenEngine::TweenEquations::easeOutQuart)
  446. .start(m_tweenManager);
  447. TweenEngine::Tween::to(m_screenshotTopBottom, util3ds::TweenSprite::POSITION_XY, 0.5f)
  448. .targetRelative(-6.f, -6.f)
  449. .ease(TweenEngine::TweenEquations::easeOutQuart)
  450. .start(m_tweenManager);
  451. TweenEngine::Tween::to(m_screenshotBottom, util3ds::TweenSprite::SCALE_XY, 0.5f)
  452. .target(0.18f * scaleBotX / 1.f, 0.18f * scaleY / 1.f)
  453. .ease(TweenEngine::TweenEquations::easeOutQuart)
  454. .start(m_tweenManager);
  455. TweenEngine::Tween::to(m_screenshotBottom, util3ds::TweenSprite::POSITION_XY, 0.5f)
  456. .targetRelative(-6.f, 0.f)
  457. .ease(TweenEngine::TweenEquations::easeOutQuart)
  458. .start(m_tweenManager);
  459. TweenEngine::Tween::to(m_screenshotTopTop, util3ds::TweenSprite::SCALE_XY, 0.7f)
  460. .target(1.f * scaleTopX / 1.f, 1.f * scaleY / 1.f)
  461. .delay(0.5f)
  462. .start(m_tweenManager);
  463. TweenEngine::Tween::to(m_screenshotTopTop, util3ds::TweenSprite::POSITION_XY, 0.7f)
  464. .target(0.f, 0.f)
  465. .delay(0.5f)
  466. .start(m_tweenManager);
  467. TweenEngine::Tween::to(m_screenshotTopBottom, util3ds::TweenSprite::SCALE_XY, 0.7f)
  468. .target(1.f * scaleTopX / 1.f, 1.f * scaleY / 1.f)
  469. .delay(0.5f)
  470. .start(m_tweenManager);
  471. TweenEngine::Tween::to(m_screenshotTopBottom, util3ds::TweenSprite::POSITION_XY, 0.7f)
  472. .target(-40.f, -240.f)
  473. .delay(0.5f)
  474. .start(m_tweenManager);
  475. TweenEngine::Tween::to(m_screenshotBottom, util3ds::TweenSprite::SCALE_XY, 0.7f)
  476. .target(1.f * scaleBotX / 1.f, 1.f * scaleY / 1.f)
  477. .delay(0.5f)
  478. .start(m_tweenManager);
  479. TweenEngine::Tween::to(m_screenshotBottom, util3ds::TweenSprite::POSITION_XY, 0.7f)
  480. .target(0.f, 0.f)
  481. .delay(0.5f)
  482. .start(m_tweenManager);
  483. }
  484. }
  485. else if (m_currentScreenshot != 0)
  486. {
  487. // Close screenshot
  488. cpp3ds::Sprite* screenshotTop = m_screenshotTops[m_currentScreenshot-1].get();
  489. cpp3ds::Sprite* screenshotBottom = m_screenshotBottoms[m_currentScreenshot-1].get();
  490. SCREENSHOT_BUTTON_FADEOUT(m_close);
  491. SCREENSHOT_BUTTON_FADEOUT(m_arrowLeft);
  492. SCREENSHOT_BUTTON_FADEOUT(m_arrowRight);
  493. // Calculate scale for screenshots that are not 400x240 (top screen) / 320x240 (bottom screen)
  494. float scaleTopX = 400.f / m_screenshotTopBottom.getTexture()->getSize().x;
  495. float scaleBotX = 320.f / m_screenshotBottom.getTexture()->getSize().x;
  496. float scaleY = 240.f / m_screenshotTopBottom.getTexture()->getSize().y;
  497. TweenEngine::Tween::to(m_screenshotTopTop, util3ds::TweenSprite::SCALE_XY, 0.7f)
  498. .target(0.15f * scaleTopX / 1.f, 0.15f * scaleY / 1.f)
  499. .start(m_tweenManager);
  500. TweenEngine::Tween::to(m_screenshotTopTop, util3ds::TweenSprite::POSITION_XY, 0.7f)
  501. .target(screenshotTop->getPosition().x, screenshotTop->getPosition().y + 270.f)
  502. .start(m_tweenManager);
  503. TweenEngine::Tween::to(m_screenshotTopBottom, util3ds::TweenSprite::SCALE_XY, 0.7f)
  504. .target(0.15f * scaleTopX / 1.f, 0.15f * scaleY / 1.f)
  505. .start(m_tweenManager);
  506. TweenEngine::Tween::to(m_screenshotTopBottom, util3ds::TweenSprite::POSITION_XY, 0.7f)
  507. .target(screenshotTop->getPosition().x, screenshotTop->getPosition().y)
  508. .start(m_tweenManager);
  509. TweenEngine::Tween::to(m_screenshotBottom, util3ds::TweenSprite::SCALE_XY, 0.7f)
  510. .target(0.15f * scaleBotX / 1.f, 0.15f * scaleY / 1.f)
  511. .start(m_tweenManager);
  512. TweenEngine::Tween::to(m_screenshotBottom, util3ds::TweenSprite::POSITION_XY, 0.7f)
  513. .target(screenshotBottom->getPosition().x, screenshotBottom->getPosition().y)
  514. .setCallback(TweenEngine::TweenCallback::COMPLETE, [=](TweenEngine::BaseTween* source) {
  515. m_currentScreenshot = screenshotIndex;
  516. })
  517. .start(m_tweenManager);
  518. return;
  519. }
  520. m_currentScreenshot = screenshotIndex;
  521. }
  522. bool AppInfo::processEvent(const cpp3ds::Event &event)
  523. {
  524. if (!m_appItem)
  525. return true;
  526. m_scrollbar.processEvent(event);
  527. if (!m_currentScreenshot)
  528. m_appStats.processEvent(event);
  529. if (m_appStats.isVisible())
  530. return false;
  531. if (m_currentScreenshot)
  532. {
  533. if (event.type == cpp3ds::Event::KeyPressed)
  534. {
  535. switch (event.key.code)
  536. {
  537. case cpp3ds::Keyboard::B:
  538. setCurrentScreenshot(0);
  539. break;
  540. case cpp3ds::Keyboard::DPadRight:
  541. if (m_currentScreenshot < m_screenshotTops.size())
  542. setCurrentScreenshot(m_currentScreenshot + 1);
  543. break;
  544. case cpp3ds::Keyboard::DPadLeft:
  545. if (m_currentScreenshot > 1)
  546. setCurrentScreenshot(m_currentScreenshot - 1);
  547. break;
  548. default:
  549. break;
  550. }
  551. }
  552. else if (event.type == cpp3ds::Event::TouchBegan)
  553. {
  554. if (m_close.getGlobalBounds().contains(event.touch.x, event.touch.y))
  555. {
  556. setCurrentScreenshot(0);
  557. }
  558. else if (m_currentScreenshot > 1 && event.touch.x < 140)
  559. {
  560. setCurrentScreenshot(m_currentScreenshot - 1);
  561. }
  562. else if (m_currentScreenshot < m_screenshotTops.size() && event.touch.x > 180)
  563. {
  564. setCurrentScreenshot(m_currentScreenshot + 1);
  565. }
  566. }
  567. return false;
  568. }
  569. if (event.type == cpp3ds::Event::TouchBegan)
  570. {
  571. if (m_textIconDemo.getGlobalBounds().contains(event.touch.x, event.touch.y))
  572. {
  573. if (!m_appItem->getDemos().empty())
  574. {
  575. cpp3ds::Uint64 demoTitleId = m_appItem->getDemos()[0];
  576. cpp3ds::String appTitle = m_appItem->getTitle();
  577. if (InstalledList::isInstalled(demoTitleId))
  578. {
  579. g_browseState->requestStackPush(States::Dialog, false, [=](void *data) mutable {
  580. auto event = reinterpret_cast<DialogState::Event*>(data);
  581. if (event->type == DialogState::GetText)
  582. {
  583. auto str = reinterpret_cast<cpp3ds::String*>(event->data);
  584. *str = _("You are deleting the demo for this title:\n\n%s", appTitle.toAnsiString().c_str());
  585. return true;
  586. }
  587. else if (event->type == DialogState::GetTitle) {
  588. auto str = reinterpret_cast<cpp3ds::String *>(event->data);
  589. *str = _("Delete a demo");
  590. return true;
  591. }
  592. else if (event->type == DialogState::Response)
  593. {
  594. bool *accepted = reinterpret_cast<bool*>(event->data);
  595. if (*accepted)
  596. {
  597. m_threadUninstallDemo.launch();
  598. }
  599. return true;
  600. }
  601. return false;
  602. });
  603. }
  604. else
  605. {
  606. DownloadQueue::getInstance().addDownload(m_appItem, demoTitleId, [this](bool succeeded){
  607. updateInfo();
  608. });
  609. Notification::spawn(_("Queued demo: %s", appTitle.toAnsiString().c_str()));
  610. }
  611. }
  612. }
  613. else if (m_appItem->isInstalled())
  614. {
  615. if (m_textExecute.getGlobalBounds().contains(event.touch.x, event.touch.y))
  616. {
  617. g_requestJump = m_appItem->getTitleId();
  618. }
  619. else if (m_textDelete.getGlobalBounds().contains(event.touch.x, event.touch.y))
  620. {
  621. cpp3ds::String appTitle = m_appItem->getTitle();
  622. g_browseState->requestStackPush(States::Dialog, false, [=](void *data) mutable {
  623. auto event = reinterpret_cast<DialogState::Event*>(data);
  624. if (event->type == DialogState::GetText)
  625. {
  626. auto str = reinterpret_cast<cpp3ds::String*>(event->data);
  627. *str = _("You are deleting this game, including all save data:\n\n%s", appTitle.toAnsiString().c_str());
  628. return true;
  629. }
  630. else if (event->type == DialogState::GetTitle) {
  631. auto str = reinterpret_cast<cpp3ds::String *>(event->data);
  632. *str = _("Uninstall a game");
  633. return true;
  634. }
  635. else if (event->type == DialogState::Response)
  636. {
  637. bool *accepted = reinterpret_cast<bool*>(event->data);
  638. if (*accepted)
  639. {
  640. m_threadUninstallGame.launch();
  641. }
  642. return true;
  643. }
  644. return false;
  645. });
  646. }
  647. }
  648. else if (m_textDownload.getGlobalBounds().contains(event.touch.x, event.touch.y))
  649. {
  650. if (!DownloadQueue::getInstance().isDownloading(m_appItem))
  651. {
  652. DownloadQueue::getInstance().addDownload(m_appItem);
  653. TweenEngine::Tween::to(m_textDownload, util3ds::TweenText::FILL_COLOR_RGB, 0.5f)
  654. .target(230, 20, 20)
  655. .start(m_tweenManager);
  656. Notification::spawn(_("Queued install: %s", m_appItem->getTitle().toAnsiString().c_str()));
  657. }
  658. }
  659. else if (m_textSleepDownload.getGlobalBounds().contains(event.touch.x, event.touch.y))
  660. {
  661. if (!DownloadQueue::getInstance().isDownloading(m_appItem))
  662. {
  663. m_appItem->queueForSleepInstall(false);
  664. TweenEngine::Tween::to(m_textSleepDownload, util3ds::TweenText::FILL_COLOR_RGB, 0.5f)
  665. .target(230, 20, 20)
  666. .start(m_tweenManager);
  667. }
  668. }
  669. }
  670. for (int i = 0; i < m_screenshotTops.size(); ++i)
  671. {
  672. if (m_screenshotTops[i]->getGlobalBounds().contains(event.touch.x, event.touch.y))
  673. setCurrentScreenshot(i+1);
  674. else if (m_screenshotBottoms[i]->getGlobalBounds().contains(event.touch.x, event.touch.y))
  675. setCurrentScreenshot(i+1);
  676. }
  677. if (event.type == cpp3ds::Event::JoystickMoved)
  678. {
  679. m_descriptionVelocity = event.joystickMove.y * 2.f;
  680. }
  681. return true;
  682. }
  683. void AppInfo::update(float delta)
  684. {
  685. // if (!m_downloads.isDownloading(m_currentApp) && !m_downloads.isInstalling(m_currentApp))
  686. // {
  687. // m_textDownload.setFillColor(cpp3ds::Color::White);
  688. // }
  689. /*m_textDescription.move(0.f, m_descriptionVelocity * delta);
  690. if (m_textDescription.getPosition().y < 49.f - m_textDescription.getLocalBounds().height + 110.f)
  691. m_textDescription.setPosition(102.f, 49.f - m_textDescription.getLocalBounds().height + 110.f);
  692. if (m_textDescription.getPosition().y > 49.f)
  693. m_textDescription.setPosition(102.f, 49.f);*/
  694. m_scrollbar.update(delta);
  695. m_tweenManager.update(delta);
  696. m_appStats.update(delta);
  697. }
  698. void AppInfo::setDescription(const rapidjson::Value &jsonDescription)
  699. {
  700. // Decode description, strip newlines
  701. const char *dd = jsonDescription.GetString();
  702. cpp3ds::String description = cpp3ds::String::fromUtf8(dd, dd + jsonDescription.GetStringLength());
  703. description.replace("\n", " ");
  704. description.replace("<br>", "\n");
  705. description.replace("<BR>", "\n");
  706. description.replace("<br/>", "\n");
  707. cpp3ds::String wrappedDesc = calculateWordWrapping(description);
  708. if (Theme::isTextThemed)
  709. m_textDescriptionDrawn << Theme::secondaryTextColor;
  710. else
  711. m_textDescriptionDrawn << cpp3ds::Color(100, 100, 100, 255);
  712. m_textDescription.setString(wrappedDesc);
  713. m_textDescriptionDrawn << wrappedDesc;
  714. }
  715. void AppInfo::setScreenshots(const rapidjson::Value &jsonScreenshots)
  716. {
  717. if (jsonScreenshots.IsArray())
  718. for (int i = 0; i < jsonScreenshots.Size(); ++i)
  719. {
  720. LoadInformations::getInstance().setStatus(_("Loading screenshot #%i", i + 1));
  721. if (jsonScreenshots[i]["image_url"].Size() == 2) {
  722. addScreenshot(i, jsonScreenshots[i]["image_url"][0]);
  723. addScreenshot(i, jsonScreenshots[i]["image_url"][1]);
  724. } else {
  725. addScreenshot(i, jsonScreenshots[i]["image_url"][0]);
  726. addEmptyScreenshot(i, jsonScreenshots[i]["image_url"][0]["type"] == "lower");
  727. }
  728. }
  729. float startX = std::round((184.f - 61.f * m_screenshotTops.size()) / 2.f);
  730. for (int i = 0; i < m_screenshotTops.size(); ++i)
  731. {
  732. m_screenshotTops[i]->setPosition(startX + i * 61.f, 167.f);
  733. m_screenshotBottoms[i]->setPosition(startX + 6.f + i * 61.f, 203.f);
  734. }
  735. }
  736. void AppInfo::addScreenshot(int index, const rapidjson::Value &jsonScreenshot)
  737. {
  738. std::string url = jsonScreenshot["value"].GetString();
  739. std::string type = jsonScreenshot["type"].GetString();
  740. std::string filename = _(FREESHOP_DIR "/tmp/%s/screen%d%s.jpg", m_appItem->getTitleIdStr().c_str(), index, type.c_str());
  741. bool isUpper = type == "upper";
  742. if (!pathExists(filename.c_str()))
  743. {
  744. Download download(url, filename);
  745. download.run();
  746. }
  747. std::unique_ptr<cpp3ds::Texture> texture(new cpp3ds::Texture());
  748. std::unique_ptr<cpp3ds::Sprite> sprite(new cpp3ds::Sprite());
  749. texture->loadFromFile(filename);
  750. texture->setSmooth(true);
  751. sprite->setTexture(*texture, true);
  752. //sprite->setScale(0.15f, 0.15f);
  753. sprite->setPosition(400.f, 0.f); // Keep offscreen
  754. // Calculate scale for screenshots that are not 400x240 (top screen) / 320x240 (bottom screen)
  755. float scaleX = isUpper ? 400.f / texture->getSize().x : 320.f / texture->getSize().x;
  756. float scaleY = 240.f / texture->getSize().y;
  757. sprite->setScale(0.15f * scaleX / 1.f, 0.15f * scaleY / 1.f);
  758. if (isUpper) {
  759. m_screenshotTops.emplace_back(std::move(sprite));
  760. m_screenshotTopTextures.emplace_back(std::move(texture));
  761. } else {
  762. m_screenshotBottoms.emplace_back(std::move(sprite));
  763. m_screenshotBottomTextures.emplace_back(std::move(texture));
  764. }
  765. }
  766. void AppInfo::addEmptyScreenshot(int index, bool isUpper)
  767. {
  768. std::unique_ptr<cpp3ds::Texture> texture(new cpp3ds::Texture());
  769. std::unique_ptr<cpp3ds::Sprite> sprite(new cpp3ds::Sprite());
  770. cpp3ds::Image img;
  771. if (isUpper)
  772. img.create(400.f, 240.f, cpp3ds::Color(0, 0, 0, 128));
  773. else
  774. img.create(320.f, 240.f, cpp3ds::Color(0, 0, 0, 128));
  775. texture->loadFromImage(img);
  776. texture->setSmooth(true);
  777. sprite->setTexture(*texture, true);
  778. sprite->setScale(0.15f, 0.15f);
  779. sprite->setPosition(400.f, 0.f); // Keep offscreen
  780. if (isUpper) {
  781. m_screenshotTops.emplace_back(std::move(sprite));
  782. m_screenshotTopTextures.emplace_back(std::move(texture));
  783. } else {
  784. m_screenshotBottoms.emplace_back(std::move(sprite));
  785. m_screenshotBottomTextures.emplace_back(std::move(texture));
  786. }
  787. }
  788. void AppInfo::updateInfo()
  789. {
  790. if (m_appItem->getDemos().size() > 0)
  791. {
  792. if (InstalledList::isInstalled(m_appItem->getDemos()[0])) {
  793. m_textIconDemo.setString(L"\uf1f8");
  794. } else {
  795. m_textIconDemo.setString(L"\uf019");
  796. }
  797. }
  798. }
  799. void AppInfo::setScroll(float position)
  800. {
  801. m_scrollPos = position;
  802. m_textDescriptionDrawn.setPosition(2.f, std::round(position + 55.f));
  803. }
  804. float AppInfo::getScroll()
  805. {
  806. return m_scrollPos;
  807. }
  808. const cpp3ds::Vector2f &AppInfo::getScrollSize()
  809. {
  810. return m_scrollSize;
  811. }
  812. void AppInfo::addInfoToDescription(const rapidjson::Value &jsonTitle)
  813. {
  814. // Get publisher from game's data and put it in description
  815. if (jsonTitle.HasMember("publisher")) {
  816. m_textDescriptionDrawn << "\n\n";
  817. if (Theme::isTextThemed)
  818. m_textDescriptionDrawn << Theme::primaryTextColor;
  819. else
  820. m_textDescriptionDrawn << cpp3ds::Color::Black;
  821. m_textDescriptionDrawn << _("Publisher").toAnsiString() << "\n";
  822. if (Theme::isTextThemed)
  823. m_textDescriptionDrawn << Theme::secondaryTextColor;
  824. else
  825. m_textDescriptionDrawn << cpp3ds::Color(100, 100, 100, 255);
  826. m_textDescriptionDrawn << jsonTitle["publisher"]["name"].GetString();
  827. }
  828. // Get number of player(s) from game's data and put it in description
  829. if (jsonTitle.HasMember("number_of_players")) {
  830. m_textDescriptionDrawn << "\n\n";
  831. if (Theme::isTextThemed)
  832. m_textDescriptionDrawn << Theme::primaryTextColor;
  833. else
  834. m_textDescriptionDrawn << cpp3ds::Color::Black;
  835. m_textDescriptionDrawn << _("Players").toAnsiString() << "\n";
  836. if (Theme::isTextThemed)
  837. m_textDescriptionDrawn << Theme::secondaryTextColor;
  838. else
  839. m_textDescriptionDrawn << cpp3ds::Color(100, 100, 100, 255);
  840. cpp3ds::String nbOfPlayers = jsonTitle["number_of_players"].GetString();
  841. nbOfPlayers.replace("\n", " ");
  842. nbOfPlayers.replace("<br>", "\n");
  843. nbOfPlayers.replace("<BR>", "\n");
  844. nbOfPlayers.replace("<br/>", "\n");
  845. m_textDescriptionDrawn << nbOfPlayers;
  846. }
  847. // Init var for genre(s)
  848. std::vector<std::string> vectorAppGenres;
  849. cpp3ds::String tempGenresStorage;
  850. // Get genre(s) from game's data
  851. if (jsonTitle.HasMember("genres")) {
  852. for (int i = 0; i < jsonTitle["genres"]["genre"].Size(); ++i) {
  853. vectorAppGenres.push_back(jsonTitle["genres"]["genre"][i]["name"].GetString());
  854. }
  855. }
  856. // Sort alphabetically the genre(s)
  857. std::sort(vectorAppGenres.begin(), vectorAppGenres.end(), [=](std::string& a, std::string& b) {
  858. return a < b;
  859. });
  860. // Put genre(s) in the string
  861. for (int i = 0; i < vectorAppGenres.size(); ++i)
  862. tempGenresStorage.insert(tempGenresStorage.getSize(), _("%s, ", vectorAppGenres[i]));
  863. // Remove the last comma
  864. if (tempGenresStorage.getSize() > 2)
  865. tempGenresStorage.erase(tempGenresStorage.getSize() - 2, 2);
  866. // Put genre(s) in description
  867. if (tempGenresStorage.getSize() > 0) {
  868. m_textDescriptionDrawn << "\n\n";
  869. if (Theme::isTextThemed)
  870. m_textDescriptionDrawn << Theme::primaryTextColor;
  871. else
  872. m_textDescriptionDrawn << cpp3ds::Color::Black;
  873. m_textDescriptionDrawn << _("Genre").toAnsiString() << "\n";
  874. if (Theme::isTextThemed)
  875. m_textDescriptionDrawn << Theme::secondaryTextColor;
  876. else
  877. m_textDescriptionDrawn << cpp3ds::Color(100, 100, 100, 255);
  878. m_textDescriptionDrawn << calculateWordWrapping(tempGenresStorage);
  879. }
  880. // Init var for language(s)
  881. std::vector<std::string> vectorAppLanguage;
  882. cpp3ds::String tempLanguageStorage;
  883. // Get language(s) from game's data
  884. if (jsonTitle.HasMember("languages")) {
  885. for (int i = 0; i < jsonTitle["languages"]["language"].Size(); ++i) {
  886. vectorAppLanguage.push_back(jsonTitle["languages"]["language"][i]["name"].GetString());
  887. }
  888. }
  889. // Sort alphabetically the language(s)
  890. std::sort(vectorAppLanguage.begin(), vectorAppLanguage.end(), [=](std::string& a, std::string& b) {
  891. return a < b;
  892. });
  893. // Put language(s) in the string
  894. for (int i = 0; i < vectorAppLanguage.size(); ++i)
  895. tempLanguageStorage.insert(tempLanguageStorage.getSize(), _("%s, ", vectorAppLanguage[i]));
  896. // Remove the last comma
  897. if (tempLanguageStorage.getSize() > 2)
  898. tempLanguageStorage.erase(tempLanguageStorage.getSize() - 2, 2);
  899. // Put language(s) in description
  900. if (tempLanguageStorage.getSize() > 0) {
  901. m_textDescriptionDrawn << "\n\n";
  902. if (Theme::isTextThemed)
  903. m_textDescriptionDrawn << Theme::primaryTextColor;
  904. else
  905. m_textDescriptionDrawn << cpp3ds::Color::Black;
  906. m_textDescriptionDrawn << _("Languages").toAnsiString() << "\n";
  907. if (Theme::isTextThemed)
  908. m_textDescriptionDrawn << Theme::secondaryTextColor;
  909. else
  910. m_textDescriptionDrawn << cpp3ds::Color(100, 100, 100, 255);
  911. m_textDescriptionDrawn << calculateWordWrapping(tempLanguageStorage);
  912. }
  913. // Init var for feature(s)
  914. std::vector<std::string> vectorAppFeatures;
  915. cpp3ds::String tempFeaturesStorage;
  916. // Get feature(s) from game's data
  917. if (jsonTitle.HasMember("features")) {
  918. for (int i = 0; i < jsonTitle["features"]["feature"].Size(); ++i) {
  919. vectorAppFeatures.push_back(jsonTitle["features"]["feature"][i]["name"].GetString());
  920. }
  921. }
  922. // Sort alphabetically the feature(s)
  923. std::sort(vectorAppFeatures.begin(), vectorAppFeatures.end(), [=](std::string& a, std::string& b) {
  924. return a < b;
  925. });
  926. // Put feature(s) in the string
  927. for (int i = 0; i < vectorAppFeatures.size(); ++i)
  928. tempFeaturesStorage.insert(tempFeaturesStorage.getSize(), _("%s, ", vectorAppFeatures[i]));
  929. // Remove the last comma
  930. if (tempFeaturesStorage.getSize() > 2)
  931. tempFeaturesStorage.erase(tempFeaturesStorage.getSize() - 2, 2);
  932. // Put feature(s) in description
  933. if (tempFeaturesStorage.getSize() > 0) {
  934. m_textDescriptionDrawn << "\n\n";
  935. if (Theme::isTextThemed)
  936. m_textDescriptionDrawn << Theme::primaryTextColor;
  937. else
  938. m_textDescriptionDrawn << cpp3ds::Color::Black;
  939. m_textDescriptionDrawn << _("Compatible Features/Accessories").toAnsiString() << "\n";
  940. if (Theme::isTextThemed)
  941. m_textDescriptionDrawn << Theme::secondaryTextColor;
  942. else
  943. m_textDescriptionDrawn << cpp3ds::Color(100, 100, 100, 255);
  944. m_textDescriptionDrawn << calculateWordWrapping(tempFeaturesStorage);
  945. }
  946. // Put indication to open AppStats in description
  947. m_textDescriptionDrawn << "\n\n";
  948. if (Theme::isTextThemed)
  949. m_textDescriptionDrawn << Theme::primaryTextColor;
  950. else
  951. m_textDescriptionDrawn << cpp3ds::Color::Black;
  952. m_textDescriptionDrawn << _("Press Select to open the Ratings screen").toAnsiString();
  953. }
  954. cpp3ds::String AppInfo::calculateWordWrapping(cpp3ds::String sentence)
  955. {
  956. // Calculate word-wrapping
  957. int startPos = 0;
  958. int lastSpace = 0;
  959. auto s = sentence.toUtf8();
  960. cpp3ds::Text tmpText = m_textDescription;
  961. for (int i = 0; i < s.size(); ++i)
  962. {
  963. if (s[i] == ' ')
  964. lastSpace = i;
  965. tmpText.setString(cpp3ds::String::fromUtf8(s.begin() + startPos, s.begin() + i));
  966. if (tmpText.getLocalBounds().width > 300)
  967. {
  968. if (lastSpace != 0)
  969. {
  970. s[lastSpace] = '\n';
  971. i = startPos = lastSpace + 1;
  972. lastSpace = 0;
  973. }
  974. else
  975. {
  976. s.insert(s.begin() + i, '\n');
  977. startPos = ++i;
  978. }
  979. }
  980. }
  981. return cpp3ds::String::fromUtf8(s.begin(), s.end());
  982. }
  983. void AppInfo::setBanner(const rapidjson::Value &jsonBanner)
  984. {
  985. std::string bannerURL = jsonBanner.GetString();
  986. std::string bannerPath = _(FREESHOP_DIR "/tmp/%s/banner.jpg", m_appItem->getTitleIdStr().c_str());
  987. Download download(bannerURL, bannerPath);
  988. download.setField("Accept", "application/json");
  989. download.run();
  990. m_isBannerLoaded = true;
  991. m_gameBannerTexture.loadFromFile(bannerPath);
  992. m_gameBanner.setPosition(200.f, 120.f);
  993. m_gameBanner.setOrigin(m_gameBannerTexture.getSize().x / 2, m_gameBannerTexture.getSize().y / 2);
  994. m_gameBanner.setColor(cpp3ds::Color(255, 255, 255, 0));
  995. m_gameBanner.setScale(1.5f, 1.5f);
  996. m_gameBanner.setTexture(m_gameBannerTexture, true);
  997. cpp3ds::IntRect textureRect;
  998. m_topIcon.setColor(cpp3ds::Color(255, 255, 255, 0));
  999. m_topIcon.setTexture(*m_appItem->getIcon(textureRect), true);
  1000. m_topIcon.setTextureRect(textureRect);
  1001. m_topIcon.setScale(.5f, .5f);
  1002. m_topIcon.setOrigin(0.f, m_topIcon.getGlobalBounds().height / 2);
  1003. TweenEngine::Tween::to(m_gameBanner, util3ds::TweenSprite::COLOR_ALPHA, 0.2f)
  1004. .target(255.f)
  1005. .start(m_tweenManager);
  1006. TweenEngine::Tween::to(m_gameBanner, util3ds::TweenSprite::SCALE_XY, 0.2f)
  1007. .target(1.f, 1.f)
  1008. .start(m_tweenManager);
  1009. TweenEngine::Tween::to(m_overlay, util3ds::TweenRectangleShape::FILL_COLOR_ALPHA, 0.2f)
  1010. .target(200.f)
  1011. .start(m_tweenManager);
  1012. TweenEngine::Tween::to(m_topIcon, util3ds::TweenSprite::COLOR_ALPHA, 0.2f)
  1013. .target(255.f)
  1014. .delay(0.2f)
  1015. .start(m_tweenManager);
  1016. }
  1017. void AppInfo::closeBanner()
  1018. {
  1019. m_tweenManager.killAll();
  1020. TweenEngine::Tween::to(m_gameBanner, util3ds::TweenSprite::COLOR_ALPHA, 0.2f)
  1021. .target(0.f)
  1022. .setCallback(TweenEngine::TweenCallback::COMPLETE, [&](TweenEngine::BaseTween* source) {
  1023. m_isBannerLoaded = false;
  1024. })
  1025. .start(m_tweenManager);
  1026. TweenEngine::Tween::to(m_gameBanner, util3ds::TweenSprite::SCALE_XY, 0.2f)
  1027. .target(.8f, .8f)
  1028. .start(m_tweenManager);
  1029. TweenEngine::Tween::to(m_overlay, util3ds::TweenRectangleShape::FILL_COLOR_ALPHA, 0.2f)
  1030. .target(0.f)
  1031. .start(m_tweenManager);
  1032. TweenEngine::Tween::to(m_topIcon, util3ds::TweenSprite::COLOR_ALPHA, 0.2f)
  1033. .target(0.f)
  1034. .start(m_tweenManager);
  1035. }
  1036. void AppInfo::uninstallGame()
  1037. {
  1038. cpp3ds::String appTitle = m_appItem->getTitle();
  1039. g_browseState->blockControls(true);
  1040. setCanDraw(false);
  1041. g_browseState->requestStackPush(States::Loading);
  1042. #ifdef _3DS
  1043. FS_MediaType mediaType = ((m_appItem->getTitleId() >> 32) == TitleKeys::DSiWare) ? MEDIATYPE_NAND : MEDIATYPE_SD;
  1044. AM_DeleteTitle(mediaType, m_appItem->getTitleId());
  1045. #endif
  1046. m_appItem->setInstalled(false);
  1047. Notification::spawn(_("Deleted: %s", appTitle.toAnsiString().c_str()));
  1048. g_browseState->requestStackPop();
  1049. g_browseState->blockControls(false);
  1050. setCanDraw(true);
  1051. InstalledList::getInstance().refresh();
  1052. updateInfo();
  1053. }
  1054. void AppInfo::uninstallDemo()
  1055. {
  1056. cpp3ds::Uint64 demoTitleId = m_appItem->getDemos()[0];
  1057. cpp3ds::String appTitle = m_appItem->getTitle();
  1058. g_browseState->blockControls(true);
  1059. setCanDraw(false);
  1060. g_browseState->requestStackPush(States::Loading);
  1061. #ifdef _3DS
  1062. AM_DeleteTitle(MEDIATYPE_SD, demoTitleId);
  1063. #endif
  1064. Notification::spawn(_("Deleted demo: %s", appTitle.toAnsiString().c_str()));
  1065. g_browseState->requestStackPop();
  1066. g_browseState->blockControls(false);
  1067. setCanDraw(true);
  1068. InstalledList::getInstance().refresh();
  1069. updateInfo();
  1070. }
  1071. void AppInfo::setCanDraw(bool canDraw)
  1072. {
  1073. m_canDraw = canDraw;
  1074. }
  1075. bool AppInfo::canDraw()
  1076. {
  1077. return m_canDraw;
  1078. }
  1079. } // namespace FreeShop