SongBrowserUI.cs 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741
  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, (sortButtonTemplate.transform as RectTransform).anchoredPosition.y);
  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. try
  232. {
  233. _log.Trace("OnDidSelectLevelEvent()");
  234. if (level == null)
  235. {
  236. _log.Debug("No level selected?");
  237. return;
  238. }
  239. if (_model.Settings == null)
  240. {
  241. _log.Debug("Settings not instantiated yet?");
  242. return;
  243. }
  244. SongBrowserModel.LastSelectedLevelId = level.levelID;
  245. RefreshAddFavoriteButton(level.levelID);
  246. RefreshQuickScrollButtons();
  247. }
  248. catch (Exception e)
  249. {
  250. _log.Exception("Exception selecting song:", e);
  251. }
  252. }
  253. /// <summary>
  254. /// Pop up a delete dialog.
  255. /// </summary>
  256. private void HandleDeleteSelectedLevel()
  257. {
  258. IStandardLevel level = this._levelListViewController.selectedLevel;
  259. if (level == null)
  260. {
  261. _log.Info("No level selected, cannot delete nothing...");
  262. return;
  263. }
  264. if (level.levelID.StartsWith("Level"))
  265. {
  266. _log.Debug("Cannot delete non-custom levels.");
  267. return;
  268. }
  269. SongLoaderPlugin.OverrideClasses.CustomLevel customLevel = _model.LevelIdToCustomSongInfos[level.levelID];
  270. 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");
  271. this._deleteDialog.didFinishEvent += this.HandleDeleteDialogPromptViewControllerDidFinish;
  272. this._levelSelectionNavigationController.PresentModalViewController(this._deleteDialog, null, false);
  273. }
  274. /// <summary>
  275. /// Handle delete dialog resolution.
  276. /// </summary>
  277. /// <param name="viewController"></param>
  278. /// <param name="ok"></param>
  279. public void HandleDeleteDialogPromptViewControllerDidFinish(SimpleDialogPromptViewController viewController, bool ok)
  280. {
  281. viewController.didFinishEvent -= this.HandleDeleteDialogPromptViewControllerDidFinish;
  282. if (!ok)
  283. {
  284. viewController.DismissModalViewController(null, false);
  285. }
  286. else
  287. {
  288. string customSongsPath = Path.Combine(Environment.CurrentDirectory, "CustomSongs");
  289. IStandardLevel level = this._levelListViewController.selectedLevel;
  290. SongLoaderPlugin.OverrideClasses.CustomLevel customLevel = _model.LevelIdToCustomSongInfos[level.levelID];
  291. string songPath = customLevel.customSongInfo.path;
  292. bool isZippedSong = false;
  293. viewController.DismissModalViewController(null, false);
  294. _log.Debug("Deleting: {0}", songPath);
  295. if (!string.IsNullOrEmpty(songPath) && songPath.Contains("/.cache/"))
  296. {
  297. isZippedSong = true;
  298. }
  299. if (isZippedSong)
  300. {
  301. DirectoryInfo songHashDir = Directory.GetParent(songPath);
  302. _log.Debug("Deleting zipped song cache: {0}", songHashDir.FullName);
  303. Directory.Delete(songHashDir.FullName, true);
  304. foreach (string file in Directory.GetFiles(customSongsPath, "*.zip"))
  305. {
  306. string hash = CreateMD5FromFile(file);
  307. if (hash != null)
  308. {
  309. if (hash == songHashDir.Name)
  310. {
  311. _log.Debug("Deleting zipped song: {0}", file);
  312. File.Delete(file);
  313. break;
  314. }
  315. }
  316. }
  317. }
  318. else
  319. {
  320. FileAttributes attr = File.GetAttributes(songPath);
  321. if (attr.HasFlag(FileAttributes.Directory))
  322. {
  323. _log.Debug("Deleting song: {0}", songPath);
  324. Directory.Delete(songPath, true);
  325. }
  326. }
  327. SongLoaderPlugin.SongLoader.Instance.RemoveSongWithPath(songPath);
  328. this.UpdateSongList();
  329. this.RefreshSongList();
  330. }
  331. }
  332. /// <summary>
  333. /// Create MD5 of a file.
  334. /// </summary>
  335. /// <param name="path"></param>
  336. /// <returns></returns>
  337. public static string CreateMD5FromFile(string path)
  338. {
  339. string hash = "";
  340. if (!File.Exists(path)) return null;
  341. using (MD5 md5 = MD5.Create())
  342. {
  343. using (var stream = File.OpenRead(path))
  344. {
  345. byte[] hashBytes = md5.ComputeHash(stream);
  346. StringBuilder sb = new StringBuilder();
  347. foreach (byte hashByte in hashBytes)
  348. {
  349. sb.Append(hashByte.ToString("X2"));
  350. }
  351. hash = sb.ToString();
  352. return hash;
  353. }
  354. }
  355. }
  356. /// <summary>
  357. /// Display the search keyboard
  358. /// </summary>
  359. void ShowSearchKeyboard()
  360. {
  361. if (_searchViewController == null)
  362. {
  363. _searchViewController = UIBuilder.CreateViewController<SearchKeyboardViewController>("SearchKeyboardViewController");
  364. _searchViewController.searchButtonPressed += SearchViewControllerSearchButtonPressed;
  365. _searchViewController.backButtonPressed += SearchViewControllerbackButtonPressed;
  366. }
  367. _levelListViewController.navigationController.PresentModalViewController(_searchViewController, null, false);
  368. }
  369. /// <summary>
  370. /// Handle back button event from search keyboard.
  371. /// </summary>
  372. private void SearchViewControllerbackButtonPressed()
  373. {
  374. }
  375. /// <summary>
  376. /// Handle search.
  377. /// </summary>
  378. /// <param name="searchFor"></param>
  379. private void SearchViewControllerSearchButtonPressed(string searchFor)
  380. {
  381. _log.Debug("Searching for \"{0}\"...", searchFor);
  382. _model.Settings.searchTerms.Insert(0, searchFor);
  383. _model.Settings.Save();
  384. SongBrowserModel.LastSelectedLevelId = null;
  385. this.UpdateSongList();
  386. this.RefreshSongList();
  387. }
  388. /// <summary>
  389. /// Make big jumps in the song list.
  390. /// </summary>
  391. /// <param name="numJumps"></param>
  392. private void JumpSongList(int numJumps, float segmentPercent)
  393. {
  394. int totalSize = _model.SortedSongList.Count;
  395. int segmentSize = (int)(totalSize * segmentPercent);
  396. TableView tableView = ReflectionUtil.GetPrivateField<TableView>(_levelListTableView, "_tableView");
  397. HashSet<int> rows = tableView.GetPrivateField<HashSet<int>>("_selectedRows");
  398. int listSegment = (rows.First() / segmentSize);
  399. int newSegment = listSegment + numJumps;
  400. int newRow = 0;
  401. if (newSegment > 0)
  402. {
  403. newRow = Math.Min(newSegment * segmentSize, totalSize - 1);
  404. }
  405. _log.Debug("ListSegment: {0}, newRow: {1}", listSegment, newRow);
  406. this.SelectAndScrollToLevel(_levelListTableView, _model.SortedSongList[newRow].levelID);
  407. }
  408. /// <summary>
  409. /// Add/Remove song from favorites depending on if it already exists.
  410. /// </summary>
  411. private void ToggleSongInFavorites()
  412. {
  413. IStandardLevel songInfo = this._levelListViewController.selectedLevel;
  414. if (_model.Settings.favorites.Contains(songInfo.levelID))
  415. {
  416. _log.Info("Remove {0} from favorites", songInfo.songName);
  417. _model.Settings.favorites.Remove(songInfo.levelID);
  418. _addFavoriteButtonText = "+1";
  419. }
  420. else
  421. {
  422. _log.Info("Add {0} to favorites", songInfo.songName);
  423. _model.Settings.favorites.Add(songInfo.levelID);
  424. _addFavoriteButtonText = "-1";
  425. }
  426. UIBuilder.SetButtonText(ref _addFavoriteButton, _addFavoriteButtonText);
  427. _model.Settings.Save();
  428. }
  429. /// <summary>
  430. /// Update interactive state of the quick scroll buttons.
  431. /// </summary>
  432. private void RefreshQuickScrollButtons()
  433. {
  434. // Refresh the fast scroll buttons
  435. _pageUpTenPercent.interactable = _tableViewPageUpButton.interactable;
  436. _pageUpTenPercent.gameObject.SetActive(_tableViewPageUpButton.IsActive());
  437. _pageDownTenPercent.interactable = _tableViewPageDownButton.interactable;
  438. _pageDownTenPercent.gameObject.SetActive(_tableViewPageDownButton.IsActive());
  439. }
  440. /// <summary>
  441. /// Helper to quickly refresh add to favorites button
  442. /// </summary>
  443. /// <param name="levelId"></param>
  444. private void RefreshAddFavoriteButton(String levelId)
  445. {
  446. if (levelId == null)
  447. {
  448. _addFavoriteButtonText = "0";
  449. return;
  450. }
  451. if (_model.Settings.favorites.Contains(levelId))
  452. {
  453. _addFavoriteButtonText = "-1";
  454. }
  455. else
  456. {
  457. _addFavoriteButtonText = "+1";
  458. }
  459. UIBuilder.SetButtonText(ref _addFavoriteButton, _addFavoriteButtonText);
  460. }
  461. /// <summary>
  462. /// Adjust the UI colors.
  463. /// </summary>
  464. public void RefreshSortButtonUI()
  465. {
  466. // So far all we need to refresh is the sort buttons.
  467. foreach (SongSortButton sortButton in _sortButtonGroup)
  468. {
  469. UIBuilder.SetButtonBorder(ref sortButton.Button, Color.black);
  470. if (sortButton.SortMode == _model.Settings.sortMode)
  471. {
  472. if (_model.InvertingResults)
  473. {
  474. UIBuilder.SetButtonBorder(ref sortButton.Button, Color.red);
  475. }
  476. else
  477. {
  478. UIBuilder.SetButtonBorder(ref sortButton.Button, Color.green);
  479. }
  480. }
  481. }
  482. }
  483. /// <summary>
  484. /// Try to refresh the song list. Broken for now.
  485. /// </summary>
  486. public void RefreshSongList()
  487. {
  488. _log.Info("Refreshing the song list view.");
  489. try
  490. {
  491. if (_model.SortedSongList == null)
  492. {
  493. _log.Debug("Songs are not sorted yet, nothing to refresh.");
  494. return;
  495. }
  496. StandardLevelSO[] levels = _model.SortedSongList.ToArray();
  497. StandardLevelListViewController songListViewController = this._levelSelectionFlowCoordinator.GetPrivateField<StandardLevelListViewController>("_levelListViewController");
  498. ReflectionUtil.SetPrivateField(_levelListTableView, "_levels", levels);
  499. ReflectionUtil.SetPrivateField(songListViewController, "_levels", levels);
  500. TableView tableView = ReflectionUtil.GetPrivateField<TableView>(_levelListTableView, "_tableView");
  501. tableView.ReloadData();
  502. String selectedLevelID = null;
  503. if (SongBrowserModel.LastSelectedLevelId != null)
  504. {
  505. selectedLevelID = SongBrowserModel.LastSelectedLevelId;
  506. _log.Debug("Scrolling to row for level ID: {0}", selectedLevelID);
  507. }
  508. else
  509. {
  510. if (levels.Length > 0)
  511. {
  512. selectedLevelID = levels.FirstOrDefault().levelID;
  513. }
  514. }
  515. // HACK, seems like if 6 or less items scrolling to row causes the song list to disappear.
  516. if (levels.Length > 6 && !String.IsNullOrEmpty(selectedLevelID) && levels.Any(x => x.levelID == selectedLevelID))
  517. {
  518. SelectAndScrollToLevel(_levelListTableView, selectedLevelID);
  519. }
  520. RefreshSortButtonUI();
  521. }
  522. catch (Exception e)
  523. {
  524. _log.Exception("Exception refreshing song list:", e);
  525. }
  526. }
  527. /// <summary>
  528. /// Scroll TableView to proper row, fire events.
  529. /// </summary>
  530. /// <param name="table"></param>
  531. /// <param name="levelID"></param>
  532. private void SelectAndScrollToLevel(StandardLevelListTableView table, string levelID)
  533. {
  534. int row = table.RowNumberForLevelID(levelID);
  535. TableView tableView = table.GetComponentInChildren<TableView>();
  536. tableView.SelectRow(row, true);
  537. tableView.ScrollToRow(row, true);
  538. }
  539. /// <summary>
  540. /// Helper for updating the model (which updates the song list)c
  541. /// </summary>
  542. public void UpdateSongList()
  543. {
  544. _log.Trace("UpdateSongList()");
  545. GameplayMode gameplayMode = _levelSelectionFlowCoordinator.GetPrivateField<GameplayMode>("_gameplayMode");
  546. _model.UpdateSongLists(gameplayMode);
  547. }
  548. /// <summary>
  549. /// Not normally called by the game-engine. Dependent on SongBrowserApplication to call it.
  550. /// </summary>
  551. public void LateUpdate()
  552. {
  553. if (!this._levelListViewController.isActiveAndEnabled) return;
  554. CheckDebugUserInput();
  555. }
  556. /// <summary>
  557. /// Map some key presses directly to UI interactions to make testing easier.
  558. /// </summary>
  559. private void CheckDebugUserInput()
  560. {
  561. try
  562. {
  563. bool isShiftKeyDown = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift);
  564. // back
  565. if (Input.GetKeyDown(KeyCode.Escape))
  566. {
  567. this._levelSelectionNavigationController.DismissButtonWasPressed();
  568. }
  569. // cycle sort modes
  570. if (Input.GetKeyDown(KeyCode.T))
  571. {
  572. _sortButtonLastPushedIndex = (_sortButtonLastPushedIndex + 1) % _sortButtonGroup.Count;
  573. _sortButtonGroup[_sortButtonLastPushedIndex].Button.onClick.Invoke();
  574. }
  575. if (Input.GetKeyDown(KeyCode.S))
  576. {
  577. onSortButtonClickEvent(SongSortMode.Search);
  578. }
  579. // select current sort mode again (toggle inverting)
  580. if (Input.GetKeyDown(KeyCode.Y))
  581. {
  582. _sortButtonGroup[_sortButtonLastPushedIndex].Button.onClick.Invoke();
  583. }
  584. // delete
  585. if (Input.GetKeyDown(KeyCode.D))
  586. {
  587. if (_deleteDialog.isInViewControllerHierarchy)
  588. {
  589. return;
  590. }
  591. _deleteButton.onClick.Invoke();
  592. }
  593. // accept delete
  594. if (Input.GetKeyDown(KeyCode.B) && _deleteDialog.isInViewControllerHierarchy)
  595. {
  596. _deleteDialog.GetPrivateField<TextMeshProButton>("_okButton").button.onClick.Invoke();
  597. }
  598. // c,v can be used to get into a song
  599. if (Input.GetKeyDown(KeyCode.C))
  600. {
  601. this.SelectAndScrollToLevel(_levelListTableView, _model.SortedSongList[0].levelID);
  602. this._levelDifficultyViewController.HandleDifficultyTableViewDidSelectRow(null, 0);
  603. this._levelSelectionFlowCoordinator.HandleDifficultyViewControllerDidSelectDifficulty(_levelDifficultyViewController, _model.SortedSongList[0].GetDifficultyLevel(LevelDifficulty.Easy));
  604. }
  605. if (Input.GetKeyDown(KeyCode.V))
  606. {
  607. this._levelSelectionFlowCoordinator.HandleLevelDetailViewControllerDidPressPlayButton(this._levelDetailViewController);
  608. }
  609. // change song index
  610. if (isShiftKeyDown && Input.GetKeyDown(KeyCode.N))
  611. {
  612. _pageUpTenPercent.onClick.Invoke();
  613. }
  614. else if (Input.GetKeyDown(KeyCode.N))
  615. {
  616. _lastRow = (_lastRow - 1) != -1 ? (_lastRow - 1) % this._model.SortedSongList.Count : 0;
  617. this.SelectAndScrollToLevel(_levelListTableView, _model.SortedSongList[_lastRow].levelID);
  618. }
  619. if (isShiftKeyDown && Input.GetKeyDown(KeyCode.M))
  620. {
  621. _pageDownTenPercent.onClick.Invoke();
  622. }
  623. else if (Input.GetKeyDown(KeyCode.M))
  624. {
  625. _lastRow = (_lastRow + 1) % this._model.SortedSongList.Count;
  626. this.SelectAndScrollToLevel(_levelListTableView, _model.SortedSongList[_lastRow].levelID);
  627. }
  628. // add to favorites
  629. if (Input.GetKeyDown(KeyCode.F))
  630. {
  631. ToggleSongInFavorites();
  632. }
  633. }
  634. catch (Exception e)
  635. {
  636. _log.Exception("Debug Input caused Exception: ", e);
  637. }
  638. }
  639. }
  640. }