SongBrowserUI.cs 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979
  1. using UnityEngine;
  2. using System.Linq;
  3. using System;
  4. using System.Collections.Generic;
  5. using UnityEngine.UI;
  6. using HMUI;
  7. using VRUI;
  8. using SongBrowserPlugin.DataAccess;
  9. using System.IO;
  10. using SongLoaderPlugin;
  11. using System.Security.Cryptography;
  12. using System.Text;
  13. namespace SongBrowserPlugin.UI
  14. {
  15. /// <summary>
  16. /// Hijack the flow coordinator. Have access to all StandardLevel easily.
  17. /// </summary>
  18. public class SongBrowserUI : MonoBehaviour
  19. {
  20. // Logging
  21. public const String Name = "SongBrowserUI";
  22. private const float SEGMENT_PERCENT = 0.1f;
  23. private Logger _log = new Logger(Name);
  24. // Beat Saber UI Elements
  25. private StandardLevelSelectionFlowCoordinator _levelSelectionFlowCoordinator;
  26. private StandardLevelListViewController _levelListViewController;
  27. private StandardLevelDetailViewController _levelDetailViewController;
  28. private StandardLevelDifficultyViewController _levelDifficultyViewController;
  29. private StandardLevelSelectionNavigationController _levelSelectionNavigationController;
  30. private StandardLevelListTableView _levelListTableView;
  31. private RectTransform _tableViewRectTransform;
  32. private Button _tableViewPageUpButton;
  33. private Button _tableViewPageDownButton;
  34. private Button _playButton;
  35. // New UI Elements
  36. private List<SongSortButton> _sortButtonGroup;
  37. private Button _searchButton;
  38. private Button _playlistButton;
  39. private Button _addFavoriteButton;
  40. private SimpleDialogPromptViewController _simpleDialogPromptViewControllerPrefab;
  41. private SimpleDialogPromptViewController _deleteDialog;
  42. private Button _deleteButton;
  43. private Button _pageUpTenPercent;
  44. private Button _pageDownTenPercent;
  45. private Button _enterFolderButton;
  46. private Button _upFolderButton;
  47. private SearchKeyboardViewController _searchViewController;
  48. // Cached items
  49. private Sprite _addFavoriteSprite;
  50. private Sprite _removeFavoriteSprite;
  51. private Sprite _currentAddFavoriteButtonSprite;
  52. // Debug
  53. private int _sortButtonLastPushedIndex = 0;
  54. private int _lastRow = 0;
  55. // Model
  56. private SongBrowserModel _model;
  57. /// <summary>
  58. /// Constructor
  59. /// </summary>
  60. public SongBrowserUI() : base()
  61. {
  62. if (_model == null)
  63. {
  64. _model = new SongBrowserModel();
  65. }
  66. _model.Init();
  67. _sortButtonLastPushedIndex = (int)(_model.Settings.sortMode);
  68. }
  69. /// <summary>
  70. /// Builds the UI for this plugin.
  71. /// </summary>
  72. public void CreateUI()
  73. {
  74. _log.Trace("CreateUI()");
  75. try
  76. {
  77. if (_levelSelectionFlowCoordinator == null)
  78. {
  79. _levelSelectionFlowCoordinator = Resources.FindObjectsOfTypeAll<StandardLevelSelectionFlowCoordinator>().First();
  80. }
  81. if (_levelListViewController == null)
  82. {
  83. _levelListViewController = _levelSelectionFlowCoordinator.GetPrivateField<StandardLevelListViewController>("_levelListViewController");
  84. }
  85. if (_levelDetailViewController == null)
  86. {
  87. _levelDetailViewController = _levelSelectionFlowCoordinator.GetPrivateField<StandardLevelDetailViewController>("_levelDetailViewController");
  88. }
  89. if (_levelSelectionNavigationController == null)
  90. {
  91. _levelSelectionNavigationController = _levelSelectionFlowCoordinator.GetPrivateField<StandardLevelSelectionNavigationController>("_levelSelectionNavigationController");
  92. }
  93. if (_levelDifficultyViewController == null)
  94. {
  95. _levelDifficultyViewController = _levelSelectionFlowCoordinator.GetPrivateField<StandardLevelDifficultyViewController>("_levelDifficultyViewController");
  96. }
  97. if (_levelListTableView == null)
  98. {
  99. _levelListTableView = this._levelListViewController.GetComponentInChildren<StandardLevelListTableView>();
  100. }
  101. _playButton = _levelDetailViewController.GetComponentsInChildren<Button>().FirstOrDefault(x => x.name == "PlayButton");
  102. _simpleDialogPromptViewControllerPrefab = Resources.FindObjectsOfTypeAll<SimpleDialogPromptViewController>().First();
  103. this._deleteDialog = UnityEngine.Object.Instantiate<SimpleDialogPromptViewController>(this._simpleDialogPromptViewControllerPrefab);
  104. this._deleteDialog.gameObject.SetActive(false);
  105. this._addFavoriteSprite = Base64Sprites.Base64ToSprite(Base64Sprites.AddToFavoritesIcon);
  106. this._removeFavoriteSprite = Base64Sprites.Base64ToSprite(Base64Sprites.RemoveFromFavoritesIcon);
  107. this.CreateUIElements();
  108. _levelListViewController.didSelectLevelEvent += OnDidSelectLevelEvent;
  109. }
  110. catch (Exception e)
  111. {
  112. _log.Exception("Exception during CreateUI: ", e);
  113. }
  114. }
  115. /// <summary>
  116. /// Builds the SongBrowser UI
  117. /// </summary>
  118. private void CreateUIElements()
  119. {
  120. _log.Trace("CreateUIElements");
  121. try
  122. {
  123. // Gather some transforms and templates to use.
  124. RectTransform sortButtonTransform = this._levelSelectionNavigationController.transform as RectTransform;
  125. RectTransform otherButtonTransform = this._levelDetailViewController.transform as RectTransform;
  126. Button sortButtonTemplate = _playButton;
  127. Button otherButtonTemplate = Resources.FindObjectsOfTypeAll<Button>().First(x => (x.name == "QuitButton"));
  128. // Resize some of the UI
  129. _tableViewRectTransform = _levelListViewController.GetComponentsInChildren<RectTransform>().First(x => x.name == "TableViewContainer");
  130. _tableViewRectTransform.sizeDelta = new Vector2(0f, -20f);
  131. _tableViewRectTransform.anchoredPosition = new Vector2(0f, -2.5f);
  132. _tableViewPageUpButton = _tableViewRectTransform.GetComponentsInChildren<Button>().First(x => x.name == "PageUpButton");
  133. (_tableViewPageUpButton.transform as RectTransform).anchoredPosition = new Vector2(0f, -1f);
  134. _tableViewPageDownButton = _tableViewRectTransform.GetComponentsInChildren<Button>().First(x => x.name == "PageDownButton");
  135. (_tableViewPageDownButton.transform as RectTransform).anchoredPosition = new Vector2(0f, 1f);
  136. // Create Sorting Songs By-Buttons
  137. _log.Debug("Creating sort by buttons...");
  138. Sprite arrowIcon = SongBrowserApplication.Instance.CachedIcons["ArrowIcon"];
  139. float fontSize = 2.35f;
  140. float buttonWidth = 13.5f;
  141. float buttonHeight = 5.250f;
  142. float buttonX = -61;
  143. float buttonY = 74.5f;
  144. string[] buttonNames = new string[]
  145. {
  146. "Favorite", "Song", "Author", "Original", "Newest", "Plays", "Difficult", "Random"
  147. };
  148. SongSortMode[] sortModes = new SongSortMode[]
  149. {
  150. SongSortMode.Favorites, SongSortMode.Default, SongSortMode.Author, SongSortMode.Original, SongSortMode.Newest, SongSortMode.PlayCount, SongSortMode.Difficulty, SongSortMode.Random
  151. };
  152. System.Action<SongSortMode>[] onClickEvents = new Action<SongSortMode>[]
  153. {
  154. onSortButtonClickEvent, onSortButtonClickEvent, onSortButtonClickEvent, onSortButtonClickEvent, onSortButtonClickEvent, onSortButtonClickEvent, onSortButtonClickEvent, onSortButtonClickEvent
  155. };
  156. _sortButtonGroup = new List<SongSortButton>();
  157. for (int i = 0; i < buttonNames.Length; i++)
  158. {
  159. _sortButtonGroup.Add(UIBuilder.CreateSortButton(sortButtonTransform, sortButtonTemplate, arrowIcon,
  160. buttonNames[i],
  161. fontSize,
  162. buttonX + (buttonWidth * i),
  163. buttonY,
  164. buttonWidth,
  165. buttonHeight,
  166. sortModes[i],
  167. onClickEvents[i]));
  168. }
  169. // Create playlist button
  170. Vector2 iconButtonSize = new Vector2(5.5f, buttonHeight);
  171. Vector2 playlistButtonSize = new Vector2(5.5f, buttonHeight);
  172. float playlistButtonX = buttonX + (buttonWidth * (buttonNames.Length - 1)) + (buttonWidth / 2.0f) + 2.5f;
  173. Sprite playlistSprite = Base64Sprites.Base64ToSprite(Base64Sprites.PlaylistIcon);
  174. _playlistButton = UIBuilder.CreateIconButton(sortButtonTransform, sortButtonTemplate, playlistSprite,
  175. new Vector2(playlistButtonX, buttonY),
  176. new Vector2(iconButtonSize.x, iconButtonSize.y),
  177. new Vector2(0, 0),
  178. new Vector2(3.5f, 3.5f),
  179. new Vector2(1.0f, 1.0f),
  180. 0.0f);
  181. _playlistButton.onClick.AddListener(delegate ()
  182. {
  183. onPlaylistButtonClickEvent(SongSortMode.Search);
  184. });
  185. buttonX += iconButtonSize.x;
  186. // Create search button
  187. float searchButtonX = playlistButtonX + iconButtonSize.x;
  188. Sprite searchSprite = Base64Sprites.Base64ToSprite(Base64Sprites.SearchIcon);
  189. _searchButton = UIBuilder.CreateIconButton(sortButtonTransform, sortButtonTemplate, searchSprite,
  190. new Vector2(searchButtonX, buttonY),
  191. new Vector2(iconButtonSize.x, iconButtonSize.y),
  192. new Vector2(0, 0),
  193. new Vector2(3.5f, 3.5f),
  194. new Vector2(1.0f, 1.0f),
  195. 0.0f);
  196. _searchButton.onClick.AddListener(delegate()
  197. {
  198. onSearchButtonClickEvent(SongSortMode.Search);
  199. });
  200. buttonX += iconButtonSize.x;
  201. // Create Add to Favorites Button
  202. Vector2 addFavoritePos = new Vector2(40f, (sortButtonTemplate.transform as RectTransform).anchoredPosition.y);
  203. _addFavoriteButton = UIBuilder.CreateIconButton(otherButtonTransform, otherButtonTemplate, null,
  204. new Vector2(addFavoritePos.x, addFavoritePos.y),
  205. new Vector2(10.0f, 10.0f),
  206. new Vector2(2f, -1.5f),
  207. new Vector2(7.0f, 7.0f),
  208. new Vector2(1.0f, 1.0f),
  209. 0.0f);
  210. _addFavoriteButton.onClick.AddListener(delegate () {
  211. ToggleSongInFavorites();
  212. });
  213. if (_currentAddFavoriteButtonSprite == null)
  214. {
  215. IStandardLevel level = this._levelListViewController.selectedLevel;
  216. if (level != null)
  217. {
  218. RefreshAddFavoriteButton(level.levelID);
  219. }
  220. }
  221. // Create delete button
  222. _deleteButton = UIBuilder.CreateButton(otherButtonTransform, otherButtonTemplate, "Delete", fontSize, 46f, 0f, 15f, 5f);
  223. _deleteButton.onClick.AddListener(delegate () {
  224. HandleDeleteSelectedLevel();
  225. });
  226. // Create fast scroll buttons
  227. _pageUpTenPercent = UIBuilder.CreateIconButton(sortButtonTransform, otherButtonTemplate, arrowIcon,
  228. new Vector2(15, 67.5f),
  229. new Vector2(6.0f, 5.5f),
  230. new Vector2(0f, 0f),
  231. new Vector2(1.5f, 1.5f),
  232. new Vector2(2.0f, 2.0f),
  233. 180);
  234. _pageUpTenPercent.onClick.AddListener(delegate () {
  235. this.JumpSongList(-1, SEGMENT_PERCENT);
  236. });
  237. _pageDownTenPercent = UIBuilder.CreateIconButton(sortButtonTransform, otherButtonTemplate, arrowIcon,
  238. new Vector2(15, 0.5f),
  239. new Vector2(6.0f, 5.5f),
  240. new Vector2(0f, 0f),
  241. new Vector2(1.5f, 1.5f),
  242. new Vector2(2.0f, 2.0f),
  243. 0);
  244. _pageDownTenPercent.onClick.AddListener(delegate () {
  245. this.JumpSongList(1, SEGMENT_PERCENT);
  246. });
  247. // Create enter folder button
  248. if (_model.Settings.folderSupportEnabled)
  249. {
  250. _enterFolderButton = UIBuilder.CreateUIButton(otherButtonTransform, _playButton);
  251. _enterFolderButton.onClick.AddListener(delegate ()
  252. {
  253. _model.PushDirectory(_levelListViewController.selectedLevel);
  254. this.RefreshSongList();
  255. this.RefreshDirectoryButtons();
  256. });
  257. UIBuilder.SetButtonText(ref _enterFolderButton, "Enter");
  258. // Create up folder button
  259. _upFolderButton = UIBuilder.CreateIconButton(sortButtonTransform, sortButtonTemplate, arrowIcon,
  260. new Vector2(searchButtonX + iconButtonSize.x, buttonY),
  261. new Vector2(iconButtonSize.x, iconButtonSize.y),
  262. new Vector2(0f, 0f),
  263. new Vector2(0.85f, 0.85f),
  264. new Vector2(2.0f, 2.0f),
  265. 180);
  266. _upFolderButton.onClick.RemoveAllListeners();
  267. _upFolderButton.onClick.AddListener(delegate ()
  268. {
  269. _model.PopDirectory();
  270. this.RefreshSongList();
  271. this.RefreshDirectoryButtons();
  272. });
  273. }
  274. RefreshSortButtonUI();
  275. RefreshDirectoryButtons();
  276. }
  277. catch (Exception e)
  278. {
  279. _log.Exception("Exception CreateUIElements:", e);
  280. }
  281. }
  282. /// <summary>
  283. /// Sort button clicked.
  284. /// </summary>
  285. private void onSortButtonClickEvent(SongSortMode sortMode)
  286. {
  287. _log.Debug("Sort button - {0} - pressed.", sortMode.ToString());
  288. _model.LastSelectedLevelId = null;
  289. if (_model.Settings.sortMode == sortMode)
  290. {
  291. _model.ToggleInverting();
  292. }
  293. _model.Settings.sortMode = sortMode;
  294. _model.Settings.Save();
  295. UpdateSongList();
  296. RefreshSongList();
  297. // Handle instant queue logic, avoid picking a folder.
  298. if (_model.Settings.sortMode == SongSortMode.Random && _model.Settings.randomInstantQueue)
  299. {
  300. for (int i = 0; i < _model.SortedSongList.Count; i++)
  301. {
  302. if (!_model.SortedSongList[i].levelID.StartsWith("Folder_"))
  303. {
  304. this.SelectAndScrollToLevel(_levelListTableView, _model.SortedSongList[i].levelID);
  305. this._levelDifficultyViewController.HandleDifficultyTableViewDidSelectRow(null, _model.SortedSongList[i].difficultyBeatmaps.Length-1);
  306. _playButton.onClick.Invoke();
  307. break;
  308. }
  309. }
  310. }
  311. }
  312. /// <summary>
  313. /// Search button clicked.
  314. /// </summary>
  315. /// <param name="sortMode"></param>
  316. private void onSearchButtonClickEvent(SongSortMode sortMode)
  317. {
  318. _model.Settings.sortMode = sortMode;
  319. _model.Settings.Save();
  320. this.ShowSearchKeyboard();
  321. }
  322. /// <summary>
  323. /// Display the playlist selector.
  324. /// </summary>
  325. /// <param name="sortMode"></param>
  326. private void onPlaylistButtonClickEvent(SongSortMode sortMode)
  327. {
  328. _log.Debug("Sort button - {0} - pressed.", sortMode.ToString());
  329. _model.LastSelectedLevelId = null;
  330. PlaylistFlowCoordinator view = UIBuilder.CreateFlowCoordinator<PlaylistFlowCoordinator>("PlaylistFlowCoordinator");
  331. view.didSelectPlaylist += HandleDidSelectPlaylist;
  332. view.Present(_levelSelectionNavigationController);
  333. }
  334. /// <summary>
  335. /// Adjust UI based on level selected.
  336. /// Various ways of detecting if a level is not properly selected. Seems most hit the first one.
  337. /// </summary>
  338. private void OnDidSelectLevelEvent(StandardLevelListViewController view, IStandardLevel level)
  339. {
  340. try
  341. {
  342. _log.Trace("OnDidSelectLevelEvent()");
  343. if (level == null)
  344. {
  345. _log.Debug("No level selected?");
  346. return;
  347. }
  348. if (_model.Settings == null)
  349. {
  350. _log.Debug("Settings not instantiated yet?");
  351. return;
  352. }
  353. _model.LastSelectedLevelId = level.levelID;
  354. RefreshAddFavoriteButton(level.levelID);
  355. RefreshQuickScrollButtons();
  356. if (level.levelID.StartsWith("Folder_"))
  357. {
  358. _log.Debug("Folder selected! Adjust PlayButton logic...");
  359. HandleDidSelectFolderRow(level);
  360. }
  361. else
  362. {
  363. HandleDidSelectLevelRow(level);
  364. }
  365. }
  366. catch (Exception e)
  367. {
  368. _log.Exception("Exception selecting song:", e);
  369. }
  370. }
  371. /// <summary>
  372. ///
  373. /// </summary>
  374. private void HandleDidSelectFolderRow(IStandardLevel level)
  375. {
  376. _enterFolderButton.gameObject.SetActive(true);
  377. _playButton.gameObject.SetActive(false);
  378. }
  379. /// <summary>
  380. ///
  381. /// </summary>
  382. /// <param name="level"></param>
  383. private void HandleDidSelectLevelRow(IStandardLevel level)
  384. {
  385. if (_enterFolderButton != null)
  386. {
  387. _enterFolderButton.gameObject.SetActive(false);
  388. }
  389. _playButton.gameObject.SetActive(true);
  390. }
  391. /// <summary>
  392. /// Pop up a delete dialog.
  393. /// </summary>
  394. private void HandleDeleteSelectedLevel()
  395. {
  396. IStandardLevel level = this._levelListViewController.selectedLevel;
  397. if (level == null)
  398. {
  399. _log.Info("No level selected, cannot delete nothing...");
  400. return;
  401. }
  402. if (level.levelID.StartsWith("Level"))
  403. {
  404. _log.Debug("Cannot delete non-custom levels.");
  405. return;
  406. }
  407. if (level.levelID.StartsWith("Folder"))
  408. {
  409. _log.Debug("Cannot delete folders.");
  410. return;
  411. }
  412. SongLoaderPlugin.OverrideClasses.CustomLevel customLevel = _model.LevelIdToCustomSongInfos[level.levelID];
  413. this._deleteDialog.Init("Delete level warning!", String.Format("<color=#00AAFF>Permanently delete level: {0}</color>\n Do you want to continue?", customLevel.songName), "YES", "NO");
  414. this._deleteDialog.didFinishEvent += this.HandleDeleteDialogPromptViewControllerDidFinish;
  415. this._levelSelectionNavigationController.PresentModalViewController(this._deleteDialog, null, false);
  416. }
  417. /// <summary>
  418. /// Handle delete dialog resolution.
  419. /// </summary>
  420. /// <param name="viewController"></param>
  421. /// <param name="ok"></param>
  422. public void HandleDeleteDialogPromptViewControllerDidFinish(SimpleDialogPromptViewController viewController, bool ok)
  423. {
  424. viewController.didFinishEvent -= this.HandleDeleteDialogPromptViewControllerDidFinish;
  425. if (!ok)
  426. {
  427. viewController.DismissModalViewController(null, false);
  428. }
  429. else
  430. {
  431. string customSongsPath = Path.Combine(Environment.CurrentDirectory, "CustomSongs");
  432. IStandardLevel level = this._levelListViewController.selectedLevel;
  433. SongLoaderPlugin.OverrideClasses.CustomLevel customLevel = _model.LevelIdToCustomSongInfos[level.levelID];
  434. string songPath = customLevel.customSongInfo.path;
  435. bool isZippedSong = false;
  436. viewController.DismissModalViewController(null, false);
  437. _log.Debug("Deleting: {0}", songPath);
  438. if (!string.IsNullOrEmpty(songPath) && songPath.Contains("/.cache/"))
  439. {
  440. isZippedSong = true;
  441. }
  442. if (isZippedSong)
  443. {
  444. DirectoryInfo songHashDir = Directory.GetParent(songPath);
  445. _log.Debug("Deleting zipped song cache: {0}", songHashDir.FullName);
  446. Directory.Delete(songHashDir.FullName, true);
  447. foreach (string file in Directory.GetFiles(customSongsPath, "*.zip"))
  448. {
  449. string hash = CreateMD5FromFile(file);
  450. if (hash != null)
  451. {
  452. if (hash == songHashDir.Name)
  453. {
  454. _log.Debug("Deleting zipped song: {0}", file);
  455. File.Delete(file);
  456. break;
  457. }
  458. }
  459. }
  460. }
  461. else
  462. {
  463. FileAttributes attr = File.GetAttributes(songPath);
  464. if (attr.HasFlag(FileAttributes.Directory))
  465. {
  466. _log.Debug("Deleting song: {0}", songPath);
  467. Directory.Delete(songPath, true);
  468. }
  469. }
  470. _model.LastSelectedLevelId = null;
  471. SongLoaderPlugin.SongLoader.Instance.RemoveSongWithPath(songPath);
  472. this.UpdateSongList();
  473. this.RefreshSongList();
  474. }
  475. }
  476. /// <summary>
  477. /// Create MD5 of a file.
  478. /// </summary>
  479. /// <param name="path"></param>
  480. /// <returns></returns>
  481. public static string CreateMD5FromFile(string path)
  482. {
  483. string hash = "";
  484. if (!File.Exists(path)) return null;
  485. using (MD5 md5 = MD5.Create())
  486. {
  487. using (var stream = File.OpenRead(path))
  488. {
  489. byte[] hashBytes = md5.ComputeHash(stream);
  490. StringBuilder sb = new StringBuilder();
  491. foreach (byte hashByte in hashBytes)
  492. {
  493. sb.Append(hashByte.ToString("X2"));
  494. }
  495. hash = sb.ToString();
  496. return hash;
  497. }
  498. }
  499. }
  500. /// <summary>
  501. /// Handle selection of a playlist. Show just the songs in the playlist.
  502. /// </summary>
  503. /// <param name="p"></param>
  504. private void HandleDidSelectPlaylist(Playlist p)
  505. {
  506. _log.Debug("Showing songs for playlist: {0}", p.playlistTitle);
  507. _model.Settings.sortMode = SongSortMode.Playlist;
  508. _model.CurrentPlaylist = p;
  509. _model.Settings.Save();
  510. this.UpdateSongList();
  511. this.RefreshSongList();
  512. }
  513. /// <summary>
  514. /// Display the search keyboard
  515. /// </summary>
  516. void ShowSearchKeyboard()
  517. {
  518. if (_searchViewController == null)
  519. {
  520. _searchViewController = UIBuilder.CreateViewController<SearchKeyboardViewController>("SearchKeyboardViewController");
  521. _searchViewController.searchButtonPressed += SearchViewControllerSearchButtonPressed;
  522. _searchViewController.backButtonPressed += SearchViewControllerbackButtonPressed;
  523. }
  524. _log.Debug("Presenting keyboard");
  525. _levelListViewController.navigationController.PresentModalViewController(_searchViewController, null, false);
  526. }
  527. /// <summary>
  528. /// Handle back button event from search keyboard.
  529. /// </summary>
  530. private void SearchViewControllerbackButtonPressed()
  531. {
  532. }
  533. /// <summary>
  534. /// Handle search.
  535. /// </summary>
  536. /// <param name="searchFor"></param>
  537. private void SearchViewControllerSearchButtonPressed(string searchFor)
  538. {
  539. _log.Debug("Searching for \"{0}\"...", searchFor);
  540. _model.Settings.searchTerms.Insert(0, searchFor);
  541. _model.Settings.Save();
  542. _model.LastSelectedLevelId = null;
  543. this.UpdateSongList();
  544. this.RefreshSongList();
  545. }
  546. /// <summary>
  547. /// Make big jumps in the song list.
  548. /// </summary>
  549. /// <param name="numJumps"></param>
  550. private void JumpSongList(int numJumps, float segmentPercent)
  551. {
  552. int totalSize = _model.SortedSongList.Count;
  553. int segmentSize = (int)(totalSize * segmentPercent);
  554. TableView tableView = ReflectionUtil.GetPrivateField<TableView>(_levelListTableView, "_tableView");
  555. HashSet<int> rows = tableView.GetPrivateField<HashSet<int>>("_selectedRows");
  556. int listSegment = (rows.First() / segmentSize);
  557. int newSegment = listSegment + numJumps;
  558. int newRow = 0;
  559. if (newSegment > 0)
  560. {
  561. newRow = Math.Min(newSegment * segmentSize, totalSize - 1);
  562. }
  563. _log.Debug("ListSegment: {0}, newRow: {1}", listSegment, newRow);
  564. this.SelectAndScrollToLevel(_levelListTableView, _model.SortedSongList[newRow].levelID);
  565. }
  566. /// <summary>
  567. /// Add/Remove song from favorites depending on if it already exists.
  568. /// </summary>
  569. private void ToggleSongInFavorites()
  570. {
  571. IStandardLevel songInfo = this._levelListViewController.selectedLevel;
  572. if (_model.Settings.favorites.Contains(songInfo.levelID))
  573. {
  574. _log.Info("Remove {0} from favorites", songInfo.songName);
  575. _model.Settings.favorites.Remove(songInfo.levelID);
  576. }
  577. else
  578. {
  579. _log.Info("Add {0} to favorites", songInfo.songName);
  580. _model.Settings.favorites.Add(songInfo.levelID);
  581. }
  582. RefreshAddFavoriteButton(songInfo.levelID);
  583. _model.Settings.Save();
  584. }
  585. /// <summary>
  586. /// Update interactive state of the quick scroll buttons.
  587. /// </summary>
  588. private void RefreshQuickScrollButtons()
  589. {
  590. // Refresh the fast scroll buttons
  591. _pageUpTenPercent.interactable = _tableViewPageUpButton.interactable;
  592. _pageUpTenPercent.gameObject.SetActive(_tableViewPageUpButton.IsActive());
  593. _pageDownTenPercent.interactable = _tableViewPageDownButton.interactable;
  594. _pageDownTenPercent.gameObject.SetActive(_tableViewPageDownButton.IsActive());
  595. }
  596. /// <summary>
  597. /// Helper to quickly refresh add to favorites button
  598. /// </summary>
  599. /// <param name="levelId"></param>
  600. private void RefreshAddFavoriteButton(String levelId)
  601. {
  602. if (levelId == null)
  603. {
  604. _currentAddFavoriteButtonSprite = null;
  605. }
  606. else
  607. {
  608. if (_model.Settings.favorites.Contains(levelId))
  609. {
  610. _currentAddFavoriteButtonSprite = _removeFavoriteSprite;
  611. }
  612. else
  613. {
  614. _currentAddFavoriteButtonSprite = _addFavoriteSprite;
  615. }
  616. }
  617. UIBuilder.SetButtonIcon(ref _addFavoriteButton, _currentAddFavoriteButtonSprite);
  618. }
  619. /// <summary>
  620. /// Adjust the UI colors.
  621. /// </summary>
  622. public void RefreshSortButtonUI()
  623. {
  624. // So far all we need to refresh is the sort buttons.
  625. foreach (SongSortButton sortButton in _sortButtonGroup)
  626. {
  627. UIBuilder.SetButtonBorder(ref sortButton.Button, Color.black);
  628. if (sortButton.SortMode == _model.Settings.sortMode)
  629. {
  630. if (_model.InvertingResults)
  631. {
  632. UIBuilder.SetButtonBorder(ref sortButton.Button, Color.red);
  633. }
  634. else
  635. {
  636. UIBuilder.SetButtonBorder(ref sortButton.Button, Color.green);
  637. }
  638. }
  639. }
  640. if (_model.Settings.sortMode == SongSortMode.Search)
  641. {
  642. UIBuilder.SetButtonBorder(ref _searchButton, Color.green);
  643. }
  644. else
  645. {
  646. UIBuilder.SetButtonBorder(ref _searchButton, Color.clear);
  647. }
  648. if (_model.Settings.sortMode == SongSortMode.Playlist)
  649. {
  650. UIBuilder.SetButtonBorder(ref _playlistButton, Color.green);
  651. }
  652. else
  653. {
  654. UIBuilder.SetButtonBorder(ref _playlistButton, Color.clear);
  655. }
  656. }
  657. /// <summary>
  658. /// Refresh the UI state of any directory buttons.
  659. /// </summary>
  660. public void RefreshDirectoryButtons()
  661. {
  662. // bail if no button, likely folder support not enabled.
  663. if (_upFolderButton == null)
  664. {
  665. return;
  666. }
  667. if (_model.DirStackSize > 1)
  668. {
  669. _upFolderButton.interactable = true;
  670. }
  671. else
  672. {
  673. _upFolderButton.interactable = false;
  674. }
  675. }
  676. /// <summary>
  677. /// Try to refresh the song list. Broken for now.
  678. /// </summary>
  679. public void RefreshSongList()
  680. {
  681. _log.Info("Refreshing the song list view.");
  682. try
  683. {
  684. if (_model.SortedSongList == null)
  685. {
  686. _log.Debug("Songs are not sorted yet, nothing to refresh.");
  687. return;
  688. }
  689. StandardLevelSO[] levels = _model.SortedSongList.ToArray();
  690. StandardLevelListViewController songListViewController = this._levelSelectionFlowCoordinator.GetPrivateField<StandardLevelListViewController>("_levelListViewController");
  691. ReflectionUtil.SetPrivateField(_levelListTableView, "_levels", levels);
  692. ReflectionUtil.SetPrivateField(songListViewController, "_levels", levels);
  693. TableView tableView = ReflectionUtil.GetPrivateField<TableView>(_levelListTableView, "_tableView");
  694. tableView.ReloadData();
  695. String selectedLevelID = null;
  696. if (_model.LastSelectedLevelId != null)
  697. {
  698. selectedLevelID = _model.LastSelectedLevelId;
  699. _log.Debug("Scrolling to row for level ID: {0}", selectedLevelID);
  700. }
  701. else
  702. {
  703. if (levels.Length > 0)
  704. {
  705. selectedLevelID = levels.FirstOrDefault().levelID;
  706. }
  707. }
  708. // HACK, seems like if 6 or less items scrolling to row causes the song list to disappear.
  709. if (levels.Length > 6 && !String.IsNullOrEmpty(selectedLevelID) && levels.Any(x => x.levelID == selectedLevelID))
  710. {
  711. SelectAndScrollToLevel(_levelListTableView, selectedLevelID);
  712. }
  713. RefreshSortButtonUI();
  714. }
  715. catch (Exception e)
  716. {
  717. _log.Exception("Exception refreshing song list:", e);
  718. }
  719. }
  720. /// <summary>
  721. /// Scroll TableView to proper row, fire events.
  722. /// </summary>
  723. /// <param name="table"></param>
  724. /// <param name="levelID"></param>
  725. private void SelectAndScrollToLevel(StandardLevelListTableView table, string levelID)
  726. {
  727. int row = table.RowNumberForLevelID(levelID);
  728. TableView tableView = table.GetComponentInChildren<TableView>();
  729. tableView.SelectRow(row, true);
  730. tableView.ScrollToRow(row, true);
  731. _lastRow = row;
  732. }
  733. /// <summary>
  734. /// Helper for updating the model (which updates the song list)c
  735. /// </summary>
  736. public void UpdateSongList()
  737. {
  738. _log.Trace("UpdateSongList()");
  739. GameplayMode gameplayMode = _levelSelectionFlowCoordinator.GetPrivateField<GameplayMode>("_gameplayMode");
  740. _model.UpdateSongLists(gameplayMode);
  741. this.RefreshDirectoryButtons();
  742. }
  743. /// <summary>
  744. /// Not normally called by the game-engine. Dependent on SongBrowserApplication to call it.
  745. /// </summary>
  746. public void LateUpdate()
  747. {
  748. CheckDebugUserInput();
  749. }
  750. /// <summary>
  751. /// Map some key presses directly to UI interactions to make testing easier.
  752. /// </summary>
  753. private void CheckDebugUserInput()
  754. {
  755. try
  756. {
  757. if (this._levelListViewController.isActiveAndEnabled)
  758. {
  759. bool isShiftKeyDown = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift);
  760. // back
  761. if (Input.GetKeyDown(KeyCode.Escape))
  762. {
  763. this._levelSelectionNavigationController.DismissButtonWasPressed();
  764. }
  765. // cycle sort modes
  766. if (Input.GetKeyDown(KeyCode.T))
  767. {
  768. _sortButtonLastPushedIndex = (_sortButtonLastPushedIndex + 1) % _sortButtonGroup.Count;
  769. _sortButtonGroup[_sortButtonLastPushedIndex].Button.onClick.Invoke();
  770. }
  771. if (Input.GetKeyDown(KeyCode.S))
  772. {
  773. onSearchButtonClickEvent(SongSortMode.Search);
  774. }
  775. // select current sort mode again (toggle inverting)
  776. if (Input.GetKeyDown(KeyCode.Y))
  777. {
  778. _sortButtonGroup[_sortButtonLastPushedIndex].Button.onClick.Invoke();
  779. }
  780. // playlists
  781. if (Input.GetKeyDown(KeyCode.P))
  782. {
  783. _playlistButton.onClick.Invoke();
  784. }
  785. // delete
  786. if (Input.GetKeyDown(KeyCode.D))
  787. {
  788. _deleteButton.onClick.Invoke();
  789. }
  790. // c - select difficulty for top song
  791. if (Input.GetKeyDown(KeyCode.C))
  792. {
  793. this.SelectAndScrollToLevel(_levelListTableView, _model.SortedSongList[0].levelID);
  794. this._levelDifficultyViewController.HandleDifficultyTableViewDidSelectRow(null, 0);
  795. this._levelSelectionFlowCoordinator.HandleDifficultyViewControllerDidSelectDifficulty(_levelDifficultyViewController, _model.SortedSongList[0].GetDifficultyLevel(LevelDifficulty.Easy));
  796. }
  797. // v start a song or enter a folder
  798. if (Input.GetKeyDown(KeyCode.Return))
  799. {
  800. if (_playButton.isActiveAndEnabled)
  801. {
  802. _playButton.onClick.Invoke();
  803. }
  804. else if (_enterFolderButton.isActiveAndEnabled)
  805. {
  806. _enterFolderButton.onClick.Invoke();
  807. }
  808. }
  809. // backspace - up a folder
  810. if (Input.GetKeyDown(KeyCode.Backspace))
  811. {
  812. _upFolderButton.onClick.Invoke();
  813. }
  814. // change song index
  815. if (isShiftKeyDown && Input.GetKeyDown(KeyCode.N))
  816. {
  817. _pageUpTenPercent.onClick.Invoke();
  818. }
  819. else if (Input.GetKeyDown(KeyCode.N))
  820. {
  821. _lastRow = (_lastRow - 1) != -1 ? (_lastRow - 1) % this._model.SortedSongList.Count : 0;
  822. this.SelectAndScrollToLevel(_levelListTableView, _model.SortedSongList[_lastRow].levelID);
  823. }
  824. if (isShiftKeyDown && Input.GetKeyDown(KeyCode.M))
  825. {
  826. _pageDownTenPercent.onClick.Invoke();
  827. }
  828. else if (Input.GetKeyDown(KeyCode.M))
  829. {
  830. _lastRow = (_lastRow + 1) % this._model.SortedSongList.Count;
  831. this.SelectAndScrollToLevel(_levelListTableView, _model.SortedSongList[_lastRow].levelID);
  832. }
  833. // add to favorites
  834. if (Input.GetKeyDown(KeyCode.F))
  835. {
  836. ToggleSongInFavorites();
  837. }
  838. }
  839. else if (_deleteDialog.isInViewControllerHierarchy)
  840. {
  841. // accept delete
  842. if (Input.GetKeyDown(KeyCode.Return))
  843. {
  844. _deleteDialog.GetPrivateField<TextMeshProButton>("_okButton").button.onClick.Invoke();
  845. }
  846. if (Input.GetKeyDown(KeyCode.Escape))
  847. {
  848. _deleteDialog.GetPrivateField<TextMeshProButton>("_cancelButton").button.onClick.Invoke();
  849. }
  850. }
  851. }
  852. catch (Exception e)
  853. {
  854. _log.Exception("Debug Input caused Exception: ", e);
  855. }
  856. }
  857. }
  858. }