SongBrowserUI.cs 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736
  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. // New UI Elements
  35. private List<SongSortButton> _sortButtonGroup;
  36. private Button _addFavoriteButton;
  37. private String _addFavoriteButtonText = null;
  38. private SimpleDialogPromptViewController _simpleDialogPromptViewControllerPrefab;
  39. private SimpleDialogPromptViewController _deleteDialog;
  40. private Button _deleteButton;
  41. private Button _pageUpTenPercent;
  42. private Button _pageDownTenPercent;
  43. private SearchKeyboardViewController _searchViewController;
  44. // Debug
  45. private int _sortButtonLastPushedIndex = 0;
  46. private int _lastRow = 0;
  47. // Model
  48. private SongBrowserModel _model;
  49. /// <summary>
  50. /// Constructor
  51. /// </summary>
  52. public SongBrowserUI() : base()
  53. {
  54. if (_model == null)
  55. {
  56. _model = new SongBrowserModel();
  57. }
  58. _model.Init();
  59. _sortButtonLastPushedIndex = (int)(_model.Settings.sortMode);
  60. }
  61. /// <summary>
  62. /// Builds the UI for this plugin.
  63. /// </summary>
  64. public void CreateUI()
  65. {
  66. _log.Trace("CreateUI()");
  67. try
  68. {
  69. if (_levelSelectionFlowCoordinator == null)
  70. {
  71. _levelSelectionFlowCoordinator = Resources.FindObjectsOfTypeAll<StandardLevelSelectionFlowCoordinator>().First();
  72. }
  73. if (_levelListViewController == null)
  74. {
  75. _levelListViewController = _levelSelectionFlowCoordinator.GetPrivateField<StandardLevelListViewController>("_levelListViewController");
  76. }
  77. if (_levelDetailViewController == null)
  78. {
  79. _levelDetailViewController = _levelSelectionFlowCoordinator.GetPrivateField<StandardLevelDetailViewController>("_levelDetailViewController");
  80. }
  81. if (_levelSelectionNavigationController == null)
  82. {
  83. _levelSelectionNavigationController = _levelSelectionFlowCoordinator.GetPrivateField<StandardLevelSelectionNavigationController>("_levelSelectionNavigationController");
  84. }
  85. if (_levelDifficultyViewController == null)
  86. {
  87. _levelDifficultyViewController = _levelSelectionFlowCoordinator.GetPrivateField<StandardLevelDifficultyViewController>("_levelDifficultyViewController");
  88. }
  89. if (_levelListTableView == null)
  90. {
  91. _levelListTableView = this._levelListViewController.GetComponentInChildren<StandardLevelListTableView>();
  92. }
  93. _simpleDialogPromptViewControllerPrefab = Resources.FindObjectsOfTypeAll<SimpleDialogPromptViewController>().First();
  94. this._deleteDialog = UnityEngine.Object.Instantiate<SimpleDialogPromptViewController>(this._simpleDialogPromptViewControllerPrefab);
  95. this._deleteDialog.gameObject.SetActive(false);
  96. this.CreateUIElements();
  97. _levelListViewController.didSelectLevelEvent += OnDidSelectLevelEvent;
  98. }
  99. catch (Exception e)
  100. {
  101. _log.Exception("Exception during CreateUI: ", e);
  102. }
  103. }
  104. /// <summary>
  105. /// Builds the SongBrowser UI
  106. /// </summary>
  107. private void CreateUIElements()
  108. {
  109. _log.Trace("CreateUIElements");
  110. try
  111. {
  112. // Gather some transforms and templates to use.
  113. RectTransform sortButtonTransform = this._levelSelectionNavigationController.transform as RectTransform;
  114. RectTransform otherButtonTransform = this._levelDetailViewController.transform as RectTransform;
  115. Button sortButtonTemplate = Resources.FindObjectsOfTypeAll<Button>().First(x => (x.name == "PlayButton"));
  116. Button otherButtonTemplate = Resources.FindObjectsOfTypeAll<Button>().First(x => (x.name == "QuitButton"));
  117. // Resize some of the UI
  118. _tableViewRectTransform = _levelListViewController.GetComponentsInChildren<RectTransform>().First(x => x.name == "TableViewContainer");
  119. _tableViewRectTransform.sizeDelta = new Vector2(0f, -20f);
  120. _tableViewRectTransform.anchoredPosition = new Vector2(0f, -2.5f);
  121. _tableViewPageUpButton = _tableViewRectTransform.GetComponentsInChildren<Button>().First(x => x.name == "PageUpButton");
  122. (_tableViewPageUpButton.transform as RectTransform).anchoredPosition = new Vector2(0f, -1f);
  123. _tableViewPageDownButton = _tableViewRectTransform.GetComponentsInChildren<Button>().First(x => x.name == "PageDownButton");
  124. (_tableViewPageDownButton.transform as RectTransform).anchoredPosition = new Vector2(0f, 1f);
  125. // Create Sorting Songs By-Buttons
  126. _log.Debug("Creating sort by buttons...");
  127. Sprite arrowIcon = SongBrowserApplication.Instance.CachedIcons["ArrowIcon"];
  128. float fontSize = 2.5f;
  129. float buttonWidth = 14.0f;
  130. float buttonHeight = 5.0f;
  131. float buttonX = 68.0f;
  132. float buttonY = 74.5f;
  133. string[] buttonNames = new string[]
  134. {
  135. "Favorite", "Song", "Author", "Original", "Newest", "Plays", "Random", "Search"
  136. };
  137. SongSortMode[] sortModes = new SongSortMode[]
  138. {
  139. SongSortMode.Favorites, SongSortMode.Default, SongSortMode.Author, SongSortMode.Original, SongSortMode.Newest, SongSortMode.PlayCount, SongSortMode.Random, SongSortMode.Search
  140. };
  141. System.Action<SongSortMode>[] onClickEvents = new Action<SongSortMode>[]
  142. {
  143. onSortButtonClickEvent, onSortButtonClickEvent, onSortButtonClickEvent, onSortButtonClickEvent, onSortButtonClickEvent, onSortButtonClickEvent, onSortButtonClickEvent, onSearchButtonClickEvent
  144. };
  145. _sortButtonGroup = new List<SongSortButton>();
  146. for (int i = 0; i < buttonNames.Length; i++)
  147. {
  148. _sortButtonGroup.Add(UIBuilder.CreateSortButton(sortButtonTransform, sortButtonTemplate, arrowIcon, buttonNames[i], fontSize, buttonX, buttonY, buttonWidth, buttonHeight, sortModes[i], onClickEvents[i]));
  149. buttonX -= buttonWidth;
  150. }
  151. // Creaate Add to Favorites Button
  152. _log.Debug("Creating add to favorites button...");
  153. _addFavoriteButton = UIBuilder.CreateUIButton(otherButtonTransform, otherButtonTemplate);
  154. (_addFavoriteButton.transform as RectTransform).anchoredPosition = new Vector2(40f, 5.75f);
  155. (_addFavoriteButton.transform as RectTransform).sizeDelta = new Vector2(10f, 10f);
  156. UIBuilder.SetButtonText(ref _addFavoriteButton, _addFavoriteButtonText);
  157. UIBuilder.SetButtonTextSize(ref _addFavoriteButton, fontSize);
  158. UIBuilder.SetButtonIconEnabled(ref _addFavoriteButton, false);
  159. _addFavoriteButton.onClick.RemoveAllListeners();
  160. _addFavoriteButton.onClick.AddListener(delegate () {
  161. ToggleSongInFavorites();
  162. });
  163. if (_addFavoriteButtonText == null)
  164. {
  165. IStandardLevel level = this._levelListViewController.selectedLevel;
  166. if (level != null)
  167. {
  168. RefreshAddFavoriteButton(level.levelID);
  169. }
  170. }
  171. // Create delete button
  172. _log.Debug("Creating delete button...");
  173. _deleteButton = UIBuilder.CreateUIButton(otherButtonTransform, otherButtonTemplate);
  174. (_deleteButton.transform as RectTransform).anchoredPosition = new Vector2(46f, 0f);
  175. (_deleteButton.transform as RectTransform).sizeDelta = new Vector2(15f, 5f);
  176. UIBuilder.SetButtonText(ref _deleteButton, "Delete");
  177. UIBuilder.SetButtonTextSize(ref _deleteButton, fontSize);
  178. UIBuilder.SetButtonIconEnabled(ref _deleteButton, false);
  179. _deleteButton.onClick.RemoveAllListeners();
  180. _deleteButton.onClick.AddListener(delegate () {
  181. HandleDeleteSelectedLevel();
  182. });
  183. // Create fast scroll buttons
  184. _pageUpTenPercent = UIBuilder.CreatePageButton(sortButtonTransform, otherButtonTemplate, arrowIcon, 15, 67.5f, 6.0f, 5.5f, 1.5f, 1.5f, 180);
  185. _pageUpTenPercent.onClick.AddListener(delegate () {
  186. this.JumpSongList(-1, SEGMENT_PERCENT);
  187. });
  188. _pageDownTenPercent = UIBuilder.CreatePageButton(sortButtonTransform, otherButtonTemplate, arrowIcon, 15, 0.5f, 6.0f, 5.5f, 1.5f, 1.5f, 0);
  189. _pageDownTenPercent.onClick.AddListener(delegate () {
  190. this.JumpSongList(1, SEGMENT_PERCENT);
  191. });
  192. RefreshSortButtonUI();
  193. }
  194. catch (Exception e)
  195. {
  196. _log.Exception("Exception CreateUIElements:", e);
  197. }
  198. }
  199. /// <summary>
  200. ///
  201. /// </summary>
  202. private void onSortButtonClickEvent(SongSortMode sortMode)
  203. {
  204. _log.Debug("Sort button - {0} - pressed.", sortMode.ToString());
  205. SongBrowserModel.LastSelectedLevelId = null;
  206. if (_model.Settings.sortMode == sortMode)
  207. {
  208. _model.ToggleInverting();
  209. }
  210. _model.Settings.sortMode = sortMode;
  211. _model.Settings.Save();
  212. UpdateSongList();
  213. RefreshSongList();
  214. }
  215. /// <summary>
  216. /// Saerch button clicked.
  217. /// </summary>
  218. /// <param name="sortMode"></param>
  219. private void onSearchButtonClickEvent(SongSortMode sortMode)
  220. {
  221. _model.Settings.sortMode = sortMode;
  222. _model.Settings.Save();
  223. this.ShowSearchKeyboard();
  224. }
  225. /// <summary>
  226. /// Adjust UI based on level selected.
  227. /// Various ways of detecting if a level is not properly selected. Seems most hit the first one.
  228. /// </summary>
  229. private void OnDidSelectLevelEvent(StandardLevelListViewController view, IStandardLevel level)
  230. {
  231. _log.Trace("OnDidSelectLevelEvent()");
  232. if (level == null)
  233. {
  234. _log.Debug("No level selected?");
  235. return;
  236. }
  237. if (_model.Settings == null)
  238. {
  239. _log.Debug("Settings not instantiated yet?");
  240. return;
  241. }
  242. SongBrowserModel.LastSelectedLevelId = level.levelID;
  243. RefreshAddFavoriteButton(level.levelID);
  244. RefreshQuickScrollButtons();
  245. }
  246. /// <summary>
  247. /// Pop up a delete dialog.
  248. /// </summary>
  249. private void HandleDeleteSelectedLevel()
  250. {
  251. IStandardLevel level = this._levelListViewController.selectedLevel;
  252. if (level == null)
  253. {
  254. _log.Info("No level selected, cannot delete nothing...");
  255. return;
  256. }
  257. if (level.levelID.StartsWith("Level"))
  258. {
  259. _log.Debug("Cannot delete non-custom levels.");
  260. return;
  261. }
  262. SongLoaderPlugin.OverrideClasses.CustomLevel customLevel = _model.LevelIdToCustomSongInfos[level.levelID];
  263. 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");
  264. this._deleteDialog.didFinishEvent += this.HandleDeleteDialogPromptViewControllerDidFinish;
  265. this._levelSelectionNavigationController.PresentModalViewController(this._deleteDialog, null, false);
  266. }
  267. /// <summary>
  268. /// Handle delete dialog resolution.
  269. /// </summary>
  270. /// <param name="viewController"></param>
  271. /// <param name="ok"></param>
  272. public void HandleDeleteDialogPromptViewControllerDidFinish(SimpleDialogPromptViewController viewController, bool ok)
  273. {
  274. viewController.didFinishEvent -= this.HandleDeleteDialogPromptViewControllerDidFinish;
  275. if (!ok)
  276. {
  277. viewController.DismissModalViewController(null, false);
  278. }
  279. else
  280. {
  281. string customSongsPath = Path.Combine(Environment.CurrentDirectory, "CustomSongs");
  282. IStandardLevel level = this._levelListViewController.selectedLevel;
  283. SongLoaderPlugin.OverrideClasses.CustomLevel customLevel = _model.LevelIdToCustomSongInfos[level.levelID];
  284. string songPath = customLevel.customSongInfo.path;
  285. bool isZippedSong = false;
  286. viewController.DismissModalViewController(null, false);
  287. _log.Debug("Deleting: {0}", songPath);
  288. if (!string.IsNullOrEmpty(songPath) && songPath.Contains("/.cache/"))
  289. {
  290. isZippedSong = true;
  291. }
  292. if (isZippedSong)
  293. {
  294. DirectoryInfo songHashDir = Directory.GetParent(songPath);
  295. _log.Debug("Deleting zipped song cache: {0}", songHashDir.FullName);
  296. Directory.Delete(songHashDir.FullName, true);
  297. foreach (string file in Directory.GetFiles(customSongsPath, "*.zip"))
  298. {
  299. string hash = CreateMD5FromFile(file);
  300. if (hash != null)
  301. {
  302. if (hash == songHashDir.Name)
  303. {
  304. _log.Debug("Deleting zipped song: {0}", file);
  305. File.Delete(file);
  306. break;
  307. }
  308. }
  309. }
  310. }
  311. else
  312. {
  313. FileAttributes attr = File.GetAttributes(songPath);
  314. if (attr.HasFlag(FileAttributes.Directory))
  315. {
  316. _log.Debug("Deleting song: {0}", songPath);
  317. Directory.Delete(songPath, true);
  318. }
  319. }
  320. SongLoaderPlugin.SongLoader.Instance.RemoveSongWithPath(songPath);
  321. this.UpdateSongList();
  322. this.RefreshSongList();
  323. }
  324. }
  325. /// <summary>
  326. /// Create MD5 of a file.
  327. /// </summary>
  328. /// <param name="path"></param>
  329. /// <returns></returns>
  330. public static string CreateMD5FromFile(string path)
  331. {
  332. string hash = "";
  333. if (!File.Exists(path)) return null;
  334. using (MD5 md5 = MD5.Create())
  335. {
  336. using (var stream = File.OpenRead(path))
  337. {
  338. byte[] hashBytes = md5.ComputeHash(stream);
  339. StringBuilder sb = new StringBuilder();
  340. foreach (byte hashByte in hashBytes)
  341. {
  342. sb.Append(hashByte.ToString("X2"));
  343. }
  344. hash = sb.ToString();
  345. return hash;
  346. }
  347. }
  348. }
  349. /// <summary>
  350. /// Display the search keyboard
  351. /// </summary>
  352. void ShowSearchKeyboard()
  353. {
  354. if (_searchViewController == null)
  355. {
  356. _searchViewController = UIBuilder.CreateViewController<SearchKeyboardViewController>("SearchKeyboardViewController");
  357. _searchViewController.searchButtonPressed += SearchViewControllerSearchButtonPressed;
  358. _searchViewController.backButtonPressed += SearchViewControllerbackButtonPressed;
  359. }
  360. _levelListViewController.navigationController.PresentModalViewController(_searchViewController, null, false);
  361. }
  362. /// <summary>
  363. /// Handle back button event from search keyboard.
  364. /// </summary>
  365. private void SearchViewControllerbackButtonPressed()
  366. {
  367. }
  368. /// <summary>
  369. /// Handle search.
  370. /// </summary>
  371. /// <param name="searchFor"></param>
  372. private void SearchViewControllerSearchButtonPressed(string searchFor)
  373. {
  374. _log.Debug("Searching for \"{0}\"...", searchFor);
  375. _model.Settings.searchTerms.Insert(0, searchFor);
  376. _model.Settings.Save();
  377. SongBrowserModel.LastSelectedLevelId = null;
  378. this.UpdateSongList();
  379. this.RefreshSongList();
  380. }
  381. /// <summary>
  382. /// Make big jumps in the song list.
  383. /// </summary>
  384. /// <param name="numJumps"></param>
  385. private void JumpSongList(int numJumps, float segmentPercent)
  386. {
  387. int totalSize = _model.SortedSongList.Count;
  388. int segmentSize = (int)(totalSize * segmentPercent);
  389. TableView tableView = ReflectionUtil.GetPrivateField<TableView>(_levelListTableView, "_tableView");
  390. HashSet<int> rows = tableView.GetPrivateField<HashSet<int>>("_selectedRows");
  391. int listSegment = (rows.First() / segmentSize);
  392. int newSegment = listSegment + numJumps;
  393. int newRow = 0;
  394. if (newSegment > 0)
  395. {
  396. newRow = Math.Min(newSegment * segmentSize, totalSize - 1);
  397. }
  398. _log.Debug("ListSegment: {0}, newRow: {1}", listSegment, newRow);
  399. this.SelectAndScrollToLevel(_levelListTableView, _model.SortedSongList[newRow].levelID);
  400. }
  401. /// <summary>
  402. /// Add/Remove song from favorites depending on if it already exists.
  403. /// </summary>
  404. private void ToggleSongInFavorites()
  405. {
  406. IStandardLevel songInfo = this._levelListViewController.selectedLevel;
  407. if (_model.Settings.favorites.Contains(songInfo.levelID))
  408. {
  409. _log.Info("Remove {0} from favorites", songInfo.songName);
  410. _model.Settings.favorites.Remove(songInfo.levelID);
  411. _addFavoriteButtonText = "+1";
  412. }
  413. else
  414. {
  415. _log.Info("Add {0} to favorites", songInfo.songName);
  416. _model.Settings.favorites.Add(songInfo.levelID);
  417. _addFavoriteButtonText = "-1";
  418. }
  419. UIBuilder.SetButtonText(ref _addFavoriteButton, _addFavoriteButtonText);
  420. _model.Settings.Save();
  421. }
  422. /// <summary>
  423. /// Update interactive state of the quick scroll buttons.
  424. /// </summary>
  425. private void RefreshQuickScrollButtons()
  426. {
  427. // Refresh the fast scroll buttons
  428. _pageUpTenPercent.interactable = _tableViewPageUpButton.interactable;
  429. _pageUpTenPercent.gameObject.SetActive(_tableViewPageUpButton.IsActive());
  430. _pageDownTenPercent.interactable = _tableViewPageDownButton.interactable;
  431. _pageDownTenPercent.gameObject.SetActive(_tableViewPageDownButton.IsActive());
  432. }
  433. /// <summary>
  434. /// Helper to quickly refresh add to favorites button
  435. /// </summary>
  436. /// <param name="levelId"></param>
  437. private void RefreshAddFavoriteButton(String levelId)
  438. {
  439. if (levelId == null)
  440. {
  441. _addFavoriteButtonText = "0";
  442. return;
  443. }
  444. if (_model.Settings.favorites.Contains(levelId))
  445. {
  446. _addFavoriteButtonText = "-1";
  447. }
  448. else
  449. {
  450. _addFavoriteButtonText = "+1";
  451. }
  452. UIBuilder.SetButtonText(ref _addFavoriteButton, _addFavoriteButtonText);
  453. }
  454. /// <summary>
  455. /// Adjust the UI colors.
  456. /// </summary>
  457. public void RefreshSortButtonUI()
  458. {
  459. // So far all we need to refresh is the sort buttons.
  460. foreach (SongSortButton sortButton in _sortButtonGroup)
  461. {
  462. UIBuilder.SetButtonBorder(ref sortButton.Button, Color.black);
  463. if (sortButton.SortMode == _model.Settings.sortMode)
  464. {
  465. if (_model.InvertingResults)
  466. {
  467. UIBuilder.SetButtonBorder(ref sortButton.Button, Color.red);
  468. }
  469. else
  470. {
  471. UIBuilder.SetButtonBorder(ref sortButton.Button, Color.green);
  472. }
  473. }
  474. }
  475. }
  476. /// <summary>
  477. /// Try to refresh the song list. Broken for now.
  478. /// </summary>
  479. public void RefreshSongList()
  480. {
  481. _log.Info("Refreshing the song list view.");
  482. try
  483. {
  484. if (_model.SortedSongList == null)
  485. {
  486. _log.Debug("Songs are not sorted yet, nothing to refresh.");
  487. return;
  488. }
  489. StandardLevelSO[] levels = _model.SortedSongList.ToArray();
  490. foreach (StandardLevelSO level in levels)
  491. _log.Debug(level.levelID);
  492. StandardLevelListViewController songListViewController = this._levelSelectionFlowCoordinator.GetPrivateField<StandardLevelListViewController>("_levelListViewController");
  493. ReflectionUtil.SetPrivateField(_levelListTableView, "_levels", levels);
  494. ReflectionUtil.SetPrivateField(songListViewController, "_levels", levels);
  495. TableView tableView = ReflectionUtil.GetPrivateField<TableView>(_levelListTableView, "_tableView");
  496. tableView.ReloadData();
  497. String selectedLevelID = null;
  498. if (SongBrowserModel.LastSelectedLevelId != null)
  499. {
  500. selectedLevelID = SongBrowserModel.LastSelectedLevelId;
  501. _log.Debug("Scrolling to row for level ID: {0}", selectedLevelID);
  502. }
  503. else
  504. {
  505. if (levels.Length > 0)
  506. {
  507. selectedLevelID = levels.FirstOrDefault().levelID;
  508. }
  509. }
  510. // HACK, seems like if 6 or less items scrolling to row causes the song list to disappear.
  511. if (levels.Length > 6 && !String.IsNullOrEmpty(selectedLevelID) && levels.Any(x => x.levelID == selectedLevelID))
  512. {
  513. SelectAndScrollToLevel(_levelListTableView, selectedLevelID);
  514. }
  515. RefreshSortButtonUI();
  516. }
  517. catch (Exception e)
  518. {
  519. _log.Exception("Exception refreshing song list:", e);
  520. }
  521. }
  522. /// <summary>
  523. /// Scroll TableView to proper row, fire events.
  524. /// </summary>
  525. /// <param name="table"></param>
  526. /// <param name="levelID"></param>
  527. private void SelectAndScrollToLevel(StandardLevelListTableView table, string levelID)
  528. {
  529. int row = table.RowNumberForLevelID(levelID);
  530. TableView tableView = table.GetComponentInChildren<TableView>();
  531. tableView.SelectRow(row, true);
  532. tableView.ScrollToRow(row, true);
  533. }
  534. /// <summary>
  535. /// Helper for updating the model (which updates the song list)c
  536. /// </summary>
  537. public void UpdateSongList()
  538. {
  539. _log.Trace("UpdateSongList()");
  540. GameplayMode gameplayMode = _levelSelectionFlowCoordinator.GetPrivateField<GameplayMode>("_gameplayMode");
  541. _model.UpdateSongLists(gameplayMode);
  542. }
  543. /// <summary>
  544. /// Not normally called by the game-engine. Dependent on SongBrowserApplication to call it.
  545. /// </summary>
  546. public void LateUpdate()
  547. {
  548. if (!this._levelListViewController.isActiveAndEnabled) return;
  549. CheckDebugUserInput();
  550. }
  551. /// <summary>
  552. /// Map some key presses directly to UI interactions to make testing easier.
  553. /// </summary>
  554. private void CheckDebugUserInput()
  555. {
  556. try
  557. {
  558. bool isShiftKeyDown = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift);
  559. // back
  560. if (Input.GetKeyDown(KeyCode.Escape))
  561. {
  562. this._levelSelectionNavigationController.DismissButtonWasPressed();
  563. }
  564. // cycle sort modes
  565. if (Input.GetKeyDown(KeyCode.T))
  566. {
  567. _sortButtonLastPushedIndex = (_sortButtonLastPushedIndex + 1) % _sortButtonGroup.Count;
  568. _sortButtonGroup[_sortButtonLastPushedIndex].Button.onClick.Invoke();
  569. }
  570. if (Input.GetKeyDown(KeyCode.S))
  571. {
  572. onSortButtonClickEvent(SongSortMode.Search);
  573. }
  574. // select current sort mode again (toggle inverting)
  575. if (Input.GetKeyDown(KeyCode.Y))
  576. {
  577. _sortButtonGroup[_sortButtonLastPushedIndex].Button.onClick.Invoke();
  578. }
  579. // delete
  580. if (Input.GetKeyDown(KeyCode.D))
  581. {
  582. if (_deleteDialog.isInViewControllerHierarchy)
  583. {
  584. return;
  585. }
  586. _deleteButton.onClick.Invoke();
  587. }
  588. // accept delete
  589. if (Input.GetKeyDown(KeyCode.B) && _deleteDialog.isInViewControllerHierarchy)
  590. {
  591. _deleteDialog.GetPrivateField<TextMeshProButton>("_okButton").button.onClick.Invoke();
  592. }
  593. // c,v can be used to get into a song
  594. if (Input.GetKeyDown(KeyCode.C))
  595. {
  596. this.SelectAndScrollToLevel(_levelListTableView, _model.SortedSongList[0].levelID);
  597. this._levelDifficultyViewController.HandleDifficultyTableViewDidSelectRow(null, 0);
  598. this._levelSelectionFlowCoordinator.HandleDifficultyViewControllerDidSelectDifficulty(_levelDifficultyViewController, _model.SortedSongList[0].GetDifficultyLevel(LevelDifficulty.Easy));
  599. }
  600. if (Input.GetKeyDown(KeyCode.V))
  601. {
  602. this._levelSelectionFlowCoordinator.HandleLevelDetailViewControllerDidPressPlayButton(this._levelDetailViewController);
  603. }
  604. // change song index
  605. if (isShiftKeyDown && Input.GetKeyDown(KeyCode.N))
  606. {
  607. _pageUpTenPercent.onClick.Invoke();
  608. }
  609. else if (Input.GetKeyDown(KeyCode.N))
  610. {
  611. _lastRow = (_lastRow - 1) != -1 ? (_lastRow - 1) % this._model.SortedSongList.Count : 0;
  612. this.SelectAndScrollToLevel(_levelListTableView, _model.SortedSongList[_lastRow].levelID);
  613. }
  614. if (isShiftKeyDown && Input.GetKeyDown(KeyCode.M))
  615. {
  616. _pageDownTenPercent.onClick.Invoke();
  617. }
  618. else if (Input.GetKeyDown(KeyCode.M))
  619. {
  620. _lastRow = (_lastRow + 1) % this._model.SortedSongList.Count;
  621. this.SelectAndScrollToLevel(_levelListTableView, _model.SortedSongList[_lastRow].levelID);
  622. }
  623. // add to favorites
  624. if (Input.GetKeyDown(KeyCode.F))
  625. {
  626. ToggleSongInFavorites();
  627. }
  628. }
  629. catch (Exception e)
  630. {
  631. _log.Exception("Debug Input caused Exception: ", e);
  632. }
  633. }
  634. }
  635. }