SongBrowserUI.cs 51 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282
  1. using UnityEngine;
  2. using System.Linq;
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Globalization;
  6. using UnityEngine.UI;
  7. using HMUI;
  8. using VRUI;
  9. using SongBrowserPlugin.DataAccess;
  10. using System.IO;
  11. using SongLoaderPlugin;
  12. using System.Security.Cryptography;
  13. using System.Text;
  14. using System.Text.RegularExpressions;
  15. using TMPro;
  16. namespace SongBrowserPlugin.UI
  17. {
  18. /// <summary>
  19. /// Hijack the flow coordinator. Have access to all StandardLevel easily.
  20. /// </summary>
  21. public class SongBrowserUI : MonoBehaviour
  22. {
  23. // Logging
  24. public const String Name = "SongBrowserUI";
  25. private const float SEGMENT_PERCENT = 0.1f;
  26. private const int LIST_ITEMS_VISIBLE_AT_ONCE = 6;
  27. private Logger _log = new Logger(Name);
  28. // Beat Saber UI Elements
  29. private StandardLevelSelectionFlowCoordinator _levelSelectionFlowCoordinator;
  30. private StandardLevelListViewController _levelListViewController;
  31. private StandardLevelDetailViewController _levelDetailViewController;
  32. private StandardLevelDifficultyViewController _levelDifficultyViewController;
  33. private StandardLevelSelectionNavigationController _levelSelectionNavigationController;
  34. private StandardLevelListTableView _levelListTableView;
  35. private RectTransform _tableViewRectTransform;
  36. private Button _tableViewPageUpButton;
  37. private Button _tableViewPageDownButton;
  38. private Button _playButton;
  39. private RectTransform _bmpValueTextRect;
  40. // New UI Elements
  41. private List<SongSortButton> _sortButtonGroup;
  42. private List<SongFilterButton> _filterButtonGroup;
  43. private Button _addFavoriteButton;
  44. private SimpleDialogPromptViewController _simpleDialogPromptViewControllerPrefab;
  45. private SimpleDialogPromptViewController _deleteDialog;
  46. private Button _deleteButton;
  47. private Button _pageUpFastButton;
  48. private Button _pageDownFastButton;
  49. private Button _enterFolderButton;
  50. private Button _upFolderButton;
  51. private SearchKeyboardViewController _searchViewController;
  52. private PlaylistFlowCoordinator _playListFlowCoordinator;
  53. private TextMeshProUGUI _ppText;
  54. private TextMeshProUGUI _starText;
  55. private TextMeshProUGUI _nText;
  56. // Cached items
  57. private Sprite _addFavoriteSprite;
  58. private Sprite _removeFavoriteSprite;
  59. private Sprite _currentAddFavoriteButtonSprite;
  60. // Plugin Compat checks
  61. private bool _detectedTwitchPluginQueue = false;
  62. private bool _checkedForTwitchPlugin = false;
  63. // Debug
  64. private int _sortButtonLastPushedIndex = 0;
  65. private int _lastRow = 0;
  66. // Model
  67. private SongBrowserModel _model;
  68. public SongBrowserModel Model
  69. {
  70. get
  71. {
  72. return _model;
  73. }
  74. }
  75. /// <summary>
  76. /// Constructor
  77. /// </summary>
  78. public SongBrowserUI() : base()
  79. {
  80. if (_model == null)
  81. {
  82. _model = new SongBrowserModel();
  83. }
  84. _model.Init();
  85. _sortButtonLastPushedIndex = (int)(_model.Settings.sortMode);
  86. }
  87. /// <summary>
  88. /// Builds the UI for this plugin.
  89. /// </summary>
  90. public void CreateUI()
  91. {
  92. _log.Trace("CreateUI()");
  93. try
  94. {
  95. if (_levelSelectionFlowCoordinator == null)
  96. {
  97. _levelSelectionFlowCoordinator = Resources.FindObjectsOfTypeAll<StandardLevelSelectionFlowCoordinator>().First();
  98. }
  99. if (_levelListViewController == null)
  100. {
  101. _levelListViewController = _levelSelectionFlowCoordinator.GetPrivateField<StandardLevelListViewController>("_levelListViewController");
  102. }
  103. if (_levelDetailViewController == null)
  104. {
  105. _levelDetailViewController = _levelSelectionFlowCoordinator.GetPrivateField<StandardLevelDetailViewController>("_levelDetailViewController");
  106. }
  107. if (_levelSelectionNavigationController == null)
  108. {
  109. _levelSelectionNavigationController = _levelSelectionFlowCoordinator.GetPrivateField<StandardLevelSelectionNavigationController>("_levelSelectionNavigationController");
  110. }
  111. if (_levelDifficultyViewController == null)
  112. {
  113. _levelDifficultyViewController = _levelSelectionFlowCoordinator.GetPrivateField<StandardLevelDifficultyViewController>("_levelDifficultyViewController");
  114. }
  115. if (_levelListTableView == null)
  116. {
  117. _levelListTableView = this._levelListViewController.GetComponentInChildren<StandardLevelListTableView>();
  118. }
  119. _bmpValueTextRect = Resources.FindObjectsOfTypeAll<RectTransform>().First(x => x.name == "BPMValueText");
  120. _playButton = _levelDetailViewController.GetComponentsInChildren<Button>().FirstOrDefault(x => x.name == "PlayButton");
  121. _simpleDialogPromptViewControllerPrefab = Resources.FindObjectsOfTypeAll<SimpleDialogPromptViewController>().First();
  122. this._deleteDialog = UnityEngine.Object.Instantiate<SimpleDialogPromptViewController>(this._simpleDialogPromptViewControllerPrefab);
  123. this._deleteDialog.gameObject.SetActive(false);
  124. this._addFavoriteSprite = Base64Sprites.Base64ToSprite(Base64Sprites.AddToFavoritesIcon);
  125. this._removeFavoriteSprite = Base64Sprites.Base64ToSprite(Base64Sprites.RemoveFromFavoritesIcon);
  126. this.CreateUIElements();
  127. _levelListViewController.didSelectLevelEvent += OnDidSelectLevelEvent;
  128. TableView tableView = ReflectionUtil.GetPrivateField<TableView>(_levelListTableView, "_tableView");
  129. tableView.didSelectRowEvent += HandleDidSelectTableViewRow;
  130. _levelDifficultyViewController.didSelectDifficultyEvent += OnDidSelectDifficultyEvent;
  131. }
  132. catch (Exception e)
  133. {
  134. _log.Exception("Exception during CreateUI: ", e);
  135. }
  136. }
  137. /// <summary>
  138. /// Builds the SongBrowser UI
  139. /// </summary>
  140. private void CreateUIElements()
  141. {
  142. _log.Trace("CreateUIElements");
  143. try
  144. {
  145. // Gather some transforms and templates to use.
  146. RectTransform sortButtonTransform = this._levelSelectionNavigationController.transform as RectTransform;
  147. RectTransform otherButtonTransform = this._levelDetailViewController.transform as RectTransform;
  148. Button sortButtonTemplate = _playButton;
  149. Button otherButtonTemplate = Resources.FindObjectsOfTypeAll<Button>().First(x => (x.name == "QuitButton"));
  150. Sprite arrowIcon = SongBrowserApplication.Instance.CachedIcons["ArrowIcon"];
  151. // Resize some of the UI
  152. _tableViewRectTransform = _levelListViewController.GetComponentsInChildren<RectTransform>().First(x => x.name == "TableViewContainer");
  153. _tableViewRectTransform.sizeDelta = new Vector2(0f, -20f);
  154. _tableViewRectTransform.anchoredPosition = new Vector2(0f, -2.5f);
  155. // Create Sorting Songs By-Buttons
  156. _log.Debug("Creating sort by buttons...");
  157. float fontSize = 2.35f;
  158. float buttonWidth = 13.50f;
  159. float buttonHeight = 5.5f;
  160. float buttonX = -61;
  161. float buttonY = 74.0f;
  162. string[] sortButtonNames = new string[]
  163. {
  164. "Song", "Author", "Original", "Newest", "Plays", "PP", "Difficult", "Random"
  165. };
  166. SongSortMode[] sortModes = new SongSortMode[]
  167. {
  168. SongSortMode.Default, SongSortMode.Author, SongSortMode.Original, SongSortMode.Newest, SongSortMode.PlayCount, SongSortMode.PP, SongSortMode.Difficulty, SongSortMode.Random
  169. };
  170. _sortButtonGroup = new List<SongSortButton>();
  171. for (int i = 0; i < sortButtonNames.Length; i++)
  172. {
  173. _sortButtonGroup.Add(UIBuilder.CreateSortButton(sortButtonTransform, sortButtonTemplate, arrowIcon,
  174. sortButtonNames[i],
  175. fontSize,
  176. buttonX + (buttonWidth * i),
  177. buttonY,
  178. buttonWidth,
  179. buttonHeight,
  180. sortModes[i],
  181. OnSortButtonClickEvent));
  182. }
  183. // Create filter buttons
  184. float filterButtonX = buttonX + (buttonWidth * (sortButtonNames.Length - 1)) + (buttonWidth / 2.0f) + 2.5f;
  185. Vector2 iconButtonSize = new Vector2(5.5f, buttonHeight);
  186. Sprite playlistSprite = Base64Sprites.Base64ToSprite(Base64Sprites.PlaylistIcon);
  187. Sprite searchSprite = Base64Sprites.Base64ToSprite(Base64Sprites.SearchIcon);
  188. List<Tuple<SongFilterMode, UnityEngine.Events.UnityAction, Sprite>> filterButtonSetup = new List<Tuple<SongFilterMode, UnityEngine.Events.UnityAction, Sprite>>()
  189. {
  190. Tuple.Create<SongFilterMode, UnityEngine.Events.UnityAction, Sprite>(SongFilterMode.Favorites, OnFavoriteFilterButtonClickEvent, _addFavoriteSprite),
  191. Tuple.Create<SongFilterMode, UnityEngine.Events.UnityAction, Sprite>(SongFilterMode.Playlist, OnPlaylistButtonClickEvent, playlistSprite),
  192. Tuple.Create<SongFilterMode, UnityEngine.Events.UnityAction, Sprite>(SongFilterMode.Search, OnSearchButtonClickEvent, searchSprite),
  193. };
  194. _filterButtonGroup = new List<SongFilterButton>();
  195. for (int i = 0; i < filterButtonSetup.Count; i++)
  196. {
  197. Tuple<SongFilterMode, UnityEngine.Events.UnityAction, Sprite> t = filterButtonSetup[i];
  198. Button b = UIBuilder.CreateIconButton(sortButtonTransform, sortButtonTemplate,
  199. t.Item3,
  200. new Vector2(filterButtonX + (iconButtonSize.x * i), buttonY),
  201. new Vector2(iconButtonSize.x, iconButtonSize.y),
  202. new Vector2(0, 0),
  203. new Vector2(3.5f, 3.5f),
  204. new Vector2(1.0f, 1.0f),
  205. 0);
  206. SongFilterButton filterButton = new SongFilterButton
  207. {
  208. Button = b,
  209. FilterMode = t.Item1
  210. };
  211. b.onClick.AddListener(t.Item2);
  212. _filterButtonGroup.Add(filterButton);
  213. }
  214. // Create Add to Favorites Button
  215. Vector2 addFavoritePos = new Vector2(40f, (sortButtonTemplate.transform as RectTransform).anchoredPosition.y);
  216. _addFavoriteButton = UIBuilder.CreateIconButton(otherButtonTransform, otherButtonTemplate, null,
  217. new Vector2(addFavoritePos.x, addFavoritePos.y),
  218. new Vector2(10.0f, 10.0f),
  219. new Vector2(2f, -1.5f),
  220. new Vector2(7.0f, 7.0f),
  221. new Vector2(1.0f, 1.0f),
  222. 0.0f);
  223. _addFavoriteButton.onClick.AddListener(delegate () {
  224. ToggleSongInFavorites();
  225. });
  226. if (_currentAddFavoriteButtonSprite == null)
  227. {
  228. IStandardLevel level = this._levelListViewController.selectedLevel;
  229. if (level != null)
  230. {
  231. RefreshAddFavoriteButton(level.levelID);
  232. }
  233. }
  234. // Create delete button
  235. _deleteButton = UIBuilder.CreateButton(otherButtonTransform, otherButtonTemplate, "Delete", fontSize, 46f, 0f, 15f, 5f);
  236. _deleteButton.onClick.AddListener(delegate () {
  237. HandleDeleteSelectedLevel();
  238. });
  239. // Create fast scroll buttons
  240. _pageUpFastButton = UIBuilder.CreateIconButton(sortButtonTransform, otherButtonTemplate, arrowIcon,
  241. new Vector2(15, 67.5f),
  242. new Vector2(6.0f, 5.5f),
  243. new Vector2(0f, 0f),
  244. new Vector2(1.5f, 1.5f),
  245. new Vector2(2.0f, 2.0f),
  246. 180);
  247. _pageUpFastButton.onClick.AddListener(delegate () {
  248. this.JumpSongList(-1, SEGMENT_PERCENT);
  249. });
  250. _pageDownFastButton = UIBuilder.CreateIconButton(sortButtonTransform, otherButtonTemplate, arrowIcon,
  251. new Vector2(15, 0.5f),
  252. new Vector2(6.0f, 5.5f),
  253. new Vector2(0f, 0f),
  254. new Vector2(1.5f, 1.5f),
  255. new Vector2(2.0f, 2.0f),
  256. 0);
  257. _pageDownFastButton.onClick.AddListener(delegate () {
  258. this.JumpSongList(1, SEGMENT_PERCENT);
  259. });
  260. // Create enter folder button
  261. if (_model.Settings.folderSupportEnabled)
  262. {
  263. _enterFolderButton = UIBuilder.CreateUIButton(otherButtonTransform, _playButton);
  264. _enterFolderButton.onClick.AddListener(delegate ()
  265. {
  266. _model.PushDirectory(_levelListViewController.selectedLevel);
  267. this.RefreshSongList();
  268. this.RefreshDirectoryButtons();
  269. });
  270. UIBuilder.SetButtonText(ref _enterFolderButton, "Enter");
  271. // Create up folder button
  272. _upFolderButton = UIBuilder.CreateIconButton(sortButtonTransform, sortButtonTemplate, arrowIcon,
  273. new Vector2(filterButtonX + (iconButtonSize.x* filterButtonSetup.Count), buttonY),
  274. new Vector2(iconButtonSize.x, iconButtonSize.y),
  275. new Vector2(0f, 0f),
  276. new Vector2(0.85f, 0.85f),
  277. new Vector2(2.0f, 2.0f),
  278. 180);
  279. _upFolderButton.onClick.RemoveAllListeners();
  280. _upFolderButton.onClick.AddListener(delegate ()
  281. {
  282. _model.PopDirectory();
  283. this.RefreshSongList();
  284. this.RefreshDirectoryButtons();
  285. });
  286. }
  287. RefreshSortButtonUI();
  288. RefreshDirectoryButtons();
  289. }
  290. catch (Exception e)
  291. {
  292. _log.Exception("Exception CreateUIElements:", e);
  293. }
  294. }
  295. /// <summary>
  296. /// Sort button clicked.
  297. /// </summary>
  298. private void OnSortButtonClickEvent(SongSortMode sortMode)
  299. {
  300. _log.Debug("Sort button - {0} - pressed.", sortMode.ToString());
  301. _model.LastSelectedLevelId = null;
  302. if (_model.Settings.sortMode == sortMode)
  303. {
  304. _model.ToggleInverting();
  305. }
  306. _model.Settings.sortMode = sortMode;
  307. _model.Settings.Save();
  308. //this._model.ProcessSongList();
  309. UpdateSongList();
  310. RefreshSongList();
  311. // Handle instant queue logic, avoid picking a folder.
  312. if (_model.Settings.sortMode == SongSortMode.Random && _model.Settings.randomInstantQueue)
  313. {
  314. for (int i = 0; i < _model.SortedSongList.Count; i++)
  315. {
  316. if (!_model.SortedSongList[i].levelID.StartsWith("Folder_"))
  317. {
  318. this.SelectAndScrollToLevel(_levelListTableView, _model.SortedSongList[i].levelID);
  319. this._levelDifficultyViewController.HandleDifficultyTableViewDidSelectRow(null, _model.SortedSongList[i].difficultyBeatmaps.Length-1);
  320. _playButton.onClick.Invoke();
  321. break;
  322. }
  323. }
  324. }
  325. }
  326. /// <summary>
  327. /// Filter by favorites.
  328. /// </summary>
  329. private void OnFavoriteFilterButtonClickEvent()
  330. {
  331. _log.Debug("Filter button - {0} - pressed.", SongFilterMode.Favorites.ToString());
  332. if (_model.Settings.filterMode != SongFilterMode.Favorites)
  333. {
  334. _model.Settings.filterMode = SongFilterMode.Favorites;
  335. }
  336. else
  337. {
  338. _model.Settings.filterMode = SongFilterMode.None;
  339. }
  340. _model.Settings.Save();
  341. UpdateSongList();
  342. RefreshSongList();
  343. }
  344. /// <summary>
  345. /// Filter button clicked.
  346. /// </summary>
  347. /// <param name="sortMode"></param>
  348. private void OnSearchButtonClickEvent()
  349. {
  350. _log.Debug("Filter button - {0} - pressed.", SongFilterMode.Search.ToString());
  351. if (_model.Settings.filterMode != SongFilterMode.Search)
  352. {
  353. this.ShowSearchKeyboard();
  354. }
  355. else
  356. {
  357. _model.Settings.filterMode = SongFilterMode.None;
  358. UpdateSongList();
  359. RefreshSongList();
  360. }
  361. _model.Settings.Save();
  362. }
  363. /// <summary>
  364. /// Display the playlist selector.
  365. /// </summary>
  366. /// <param name="sortMode"></param>
  367. private void OnPlaylistButtonClickEvent()
  368. {
  369. _log.Debug("Filter button - {0} - pressed.", SongFilterMode.Playlist.ToString());
  370. _model.LastSelectedLevelId = null;
  371. if (_model.Settings.filterMode != SongFilterMode.Playlist)
  372. {
  373. if (_playListFlowCoordinator == null || !_playListFlowCoordinator.isActiveAndEnabled)
  374. {
  375. _playListFlowCoordinator = UIBuilder.CreateFlowCoordinator<PlaylistFlowCoordinator>("PlaylistFlowCoordinator");
  376. _playListFlowCoordinator.didSelectPlaylist += HandleDidSelectPlaylist;
  377. _playListFlowCoordinator.Present(_levelSelectionNavigationController);
  378. }
  379. }
  380. else
  381. {
  382. _model.Settings.filterMode = SongFilterMode.None;
  383. _model.Settings.Save();
  384. UpdateSongList();
  385. RefreshSongList();
  386. }
  387. }
  388. /// <summary>
  389. /// Adjust UI based on level selected.
  390. /// Various ways of detecting if a level is not properly selected. Seems most hit the first one.
  391. /// </summary>
  392. private void OnDidSelectLevelEvent(StandardLevelListViewController view, IStandardLevel level)
  393. {
  394. try
  395. {
  396. _log.Trace("OnDidSelectLevelEvent()");
  397. if (level == null)
  398. {
  399. _log.Debug("No level selected?");
  400. return;
  401. }
  402. if (_model.Settings == null)
  403. {
  404. _log.Debug("Settings not instantiated yet?");
  405. return;
  406. }
  407. _model.LastSelectedLevelId = level.levelID;
  408. RefreshAddFavoriteButton(level.levelID);
  409. RefreshQuickScrollButtons();
  410. if (level.levelID.StartsWith("Folder_"))
  411. {
  412. _log.Debug("Folder selected! Adjust PlayButton logic...");
  413. HandleDidSelectFolderRow(level);
  414. }
  415. else
  416. {
  417. HandleDidSelectLevelRow(level);
  418. }
  419. }
  420. catch (Exception e)
  421. {
  422. _log.Exception("Exception selecting song:", e);
  423. }
  424. }
  425. /// <summary>
  426. /// Handle difficulty level selection.
  427. /// </summary>
  428. private void OnDidSelectDifficultyEvent(StandardLevelDifficultyViewController view, IStandardLevelDifficultyBeatmap beatmap)
  429. {
  430. this.RefreshScoreSaberData(_levelListViewController.selectedLevel);
  431. }
  432. /// <summary>
  433. /// Turn play button into enter folder button.
  434. /// </summary>
  435. private void HandleDidSelectFolderRow(IStandardLevel level)
  436. {
  437. _enterFolderButton.gameObject.SetActive(true);
  438. _playButton.gameObject.SetActive(false);
  439. }
  440. /// <summary>
  441. /// Turn enter folder button into play button.
  442. /// </summary>
  443. /// <param name="level"></param>
  444. private void HandleDidSelectLevelRow(IStandardLevel level)
  445. {
  446. // deal with enter folder button
  447. if (_enterFolderButton != null)
  448. {
  449. _enterFolderButton.gameObject.SetActive(false);
  450. }
  451. _playButton.gameObject.SetActive(true);
  452. this.RefreshScoreSaberData(level);
  453. }
  454. /// <summary>
  455. /// Track the current row.
  456. /// </summary>
  457. /// <param name="tableView"></param>
  458. /// <param name="row"></param>
  459. private void HandleDidSelectTableViewRow(TableView tableView, int row)
  460. {
  461. _log.Trace("HandleDidSelectTableViewRow({0})", row);
  462. _lastRow = row;
  463. }
  464. /// <summary>
  465. /// Pop up a delete dialog.
  466. /// </summary>
  467. private void HandleDeleteSelectedLevel()
  468. {
  469. IStandardLevel level = this._levelListViewController.selectedLevel;
  470. if (level == null)
  471. {
  472. _log.Info("No level selected, cannot delete nothing...");
  473. return;
  474. }
  475. if (level.levelID.StartsWith("Level"))
  476. {
  477. _log.Debug("Cannot delete non-custom levels.");
  478. return;
  479. }
  480. if (level.levelID.StartsWith("Folder"))
  481. {
  482. _log.Debug("Cannot delete folders.");
  483. return;
  484. }
  485. SongLoaderPlugin.OverrideClasses.CustomLevel customLevel = _model.LevelIdToCustomSongInfos[level.levelID];
  486. 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");
  487. this._deleteDialog.didFinishEvent += this.HandleDeleteDialogPromptViewControllerDidFinish;
  488. this._levelSelectionNavigationController.PresentModalViewController(this._deleteDialog, null, false);
  489. }
  490. /// <summary>
  491. /// Handle delete dialog resolution.
  492. /// </summary>
  493. /// <param name="viewController"></param>
  494. /// <param name="ok"></param>
  495. public void HandleDeleteDialogPromptViewControllerDidFinish(SimpleDialogPromptViewController viewController, bool ok)
  496. {
  497. viewController.didFinishEvent -= this.HandleDeleteDialogPromptViewControllerDidFinish;
  498. if (!ok)
  499. {
  500. viewController.DismissModalViewController(null, false);
  501. }
  502. else
  503. {
  504. string customSongsPath = Path.Combine(Environment.CurrentDirectory, "CustomSongs");
  505. IStandardLevel level = this._levelListViewController.selectedLevel;
  506. SongLoaderPlugin.OverrideClasses.CustomLevel customLevel = _model.LevelIdToCustomSongInfos[level.levelID];
  507. string songPath = customLevel.customSongInfo.path;
  508. bool isZippedSong = false;
  509. viewController.DismissModalViewController(null, false);
  510. _log.Debug("Deleting: {0}", songPath);
  511. if (!string.IsNullOrEmpty(songPath) && songPath.Contains("/.cache/"))
  512. {
  513. isZippedSong = true;
  514. }
  515. if (isZippedSong)
  516. {
  517. DirectoryInfo songHashDir = Directory.GetParent(songPath);
  518. _log.Debug("Deleting zipped song cache: {0}", songHashDir.FullName);
  519. Directory.Delete(songHashDir.FullName, true);
  520. foreach (string file in Directory.GetFiles(customSongsPath, "*.zip"))
  521. {
  522. string hash = CreateMD5FromFile(file);
  523. if (hash != null)
  524. {
  525. if (hash == songHashDir.Name)
  526. {
  527. _log.Debug("Deleting zipped song: {0}", file);
  528. File.Delete(file);
  529. break;
  530. }
  531. }
  532. }
  533. }
  534. else
  535. {
  536. // Just delete the song we know about.
  537. FileAttributes attr = File.GetAttributes(songPath);
  538. if (attr.HasFlag(FileAttributes.Directory))
  539. {
  540. _log.Debug("Deleting song: {0}", songPath);
  541. Directory.Delete(songPath, true);
  542. }
  543. // check if this is in the BeatSaberDownloader format
  544. if (_model.Settings.deleteNumberedSongFolder)
  545. {
  546. String[] splitPath = songPath.Split('/');
  547. if (splitPath.Length > 2)
  548. {
  549. String numberedDir = splitPath[splitPath.Length - 2];
  550. Regex r = new Regex(@"^\d{1,}-\d{1,}");
  551. if (r.Match(numberedDir).Success)
  552. {
  553. DirectoryInfo songNumberedDirPath = Directory.GetParent(songPath);
  554. _log.Debug("Deleting song numbered folder: {0}", songNumberedDirPath.FullName);
  555. Directory.Delete(songNumberedDirPath.FullName, true);
  556. }
  557. }
  558. }
  559. }
  560. int newRow = _model.SortedSongList.FindIndex(x => x.levelID == level.levelID) - 1;
  561. if (newRow > 0 && newRow < _model.SortedSongList.Count)
  562. {
  563. _model.LastSelectedLevelId = _model.SortedSongList[newRow].levelID;
  564. }
  565. else
  566. {
  567. _model.LastSelectedLevelId = null;
  568. }
  569. SongLoaderPlugin.SongLoader.Instance.RemoveSongWithPath(songPath);
  570. this.UpdateSongList();
  571. this.RefreshSongList();
  572. }
  573. }
  574. /// <summary>
  575. /// Create MD5 of a file.
  576. /// </summary>
  577. /// <param name="path"></param>
  578. /// <returns></returns>
  579. public static string CreateMD5FromFile(string path)
  580. {
  581. string hash = "";
  582. if (!File.Exists(path)) return null;
  583. using (MD5 md5 = MD5.Create())
  584. {
  585. using (var stream = File.OpenRead(path))
  586. {
  587. byte[] hashBytes = md5.ComputeHash(stream);
  588. StringBuilder sb = new StringBuilder();
  589. foreach (byte hashByte in hashBytes)
  590. {
  591. sb.Append(hashByte.ToString("X2"));
  592. }
  593. hash = sb.ToString();
  594. return hash;
  595. }
  596. }
  597. }
  598. /// <summary>
  599. /// Handle selection of a playlist. Show just the songs in the playlist.
  600. /// </summary>
  601. /// <param name="p"></param>
  602. private void HandleDidSelectPlaylist(Playlist p)
  603. {
  604. if (_playListFlowCoordinator != null)
  605. {
  606. _levelSelectionNavigationController.PopViewControllerImmediately();
  607. _playListFlowCoordinator.gameObject.SetActive(false);
  608. UnityEngine.Object.DestroyImmediate(_playListFlowCoordinator);
  609. }
  610. if (p != null)
  611. {
  612. _log.Debug("Showing songs for playlist: {0}", p.playlistTitle);
  613. _model.Settings.filterMode = SongFilterMode.Playlist;
  614. _model.CurrentPlaylist = p;
  615. _model.Settings.Save();
  616. }
  617. else
  618. {
  619. _log.Debug("No playlist selected");
  620. }
  621. this.UpdateSongList();
  622. this.RefreshSongList();
  623. }
  624. /// <summary>
  625. /// Display the search keyboard
  626. /// </summary>
  627. void ShowSearchKeyboard()
  628. {
  629. if (_searchViewController == null)
  630. {
  631. _searchViewController = UIBuilder.CreateViewController<SearchKeyboardViewController>("SearchKeyboardViewController");
  632. _searchViewController.searchButtonPressed += SearchViewControllerSearchButtonPressed;
  633. _searchViewController.backButtonPressed += SearchViewControllerbackButtonPressed;
  634. }
  635. _log.Debug("Presenting keyboard");
  636. _levelListViewController.navigationController.PresentModalViewController(_searchViewController, null, false);
  637. }
  638. /// <summary>
  639. /// Handle back button event from search keyboard.
  640. /// </summary>
  641. private void SearchViewControllerbackButtonPressed()
  642. {
  643. // force disable search filter.
  644. this._model.Settings.filterMode = SongFilterMode.None;
  645. this._model.Settings.Save();
  646. }
  647. /// <summary>
  648. /// Handle search.
  649. /// </summary>
  650. /// <param name="searchFor"></param>
  651. private void SearchViewControllerSearchButtonPressed(string searchFor)
  652. {
  653. _log.Debug("Searching for \"{0}\"...", searchFor);
  654. _model.Settings.filterMode = SongFilterMode.Search;
  655. _model.Settings.searchTerms.Insert(0, searchFor);
  656. _model.Settings.Save();
  657. _model.LastSelectedLevelId = null;
  658. this.UpdateSongList();
  659. this.RefreshSongList();
  660. }
  661. /// <summary>
  662. /// Make big jumps in the song list.
  663. /// </summary>
  664. /// <param name="numJumps"></param>
  665. private void JumpSongList(int numJumps, float segmentPercent)
  666. {
  667. int totalSize = _model.SortedSongList.Count;
  668. int segmentSize = (int)(totalSize * segmentPercent);
  669. // Jump at least one scree size.
  670. if (segmentSize < LIST_ITEMS_VISIBLE_AT_ONCE)
  671. {
  672. segmentSize = LIST_ITEMS_VISIBLE_AT_ONCE;
  673. }
  674. TableView tableView = ReflectionUtil.GetPrivateField<TableView>(_levelListTableView, "_tableView");
  675. int jumpDirection = Math.Sign(numJumps);
  676. int newRow = _lastRow + (jumpDirection * segmentSize);
  677. if (newRow <= 0)
  678. {
  679. newRow = 0;
  680. }
  681. else if (newRow >= totalSize)
  682. {
  683. newRow = totalSize - 1;
  684. }
  685. _log.Debug("jumpDirection: {0}, newRow: {1}", jumpDirection, newRow);
  686. _lastRow = newRow;
  687. this.SelectAndScrollToLevel(_levelListTableView, _model.SortedSongList[newRow].levelID);
  688. }
  689. /// <summary>
  690. /// Add/Remove song from favorites depending on if it already exists.
  691. /// </summary>
  692. private void ToggleSongInFavorites()
  693. {
  694. IStandardLevel songInfo = this._levelListViewController.selectedLevel;
  695. if (_model.Settings.Favorites.Contains(songInfo.levelID))
  696. {
  697. _log.Info("Remove {0} from favorites", songInfo.songName);
  698. _model.Settings.Favorites.Remove(songInfo.levelID);
  699. }
  700. else
  701. {
  702. _log.Info("Add {0} to favorites", songInfo.songName);
  703. _model.Settings.Favorites.Add(songInfo.levelID);
  704. }
  705. RefreshAddFavoriteButton(songInfo.levelID);
  706. _model.Settings.Save();
  707. }
  708. /// <summary>
  709. /// Update GUI elements that show score saber data.
  710. /// TODO - make better
  711. /// </summary>
  712. public void RefreshScoreSaberData(IStandardLevel level)
  713. {
  714. // TODO - fix this obvious mess...
  715. // use controllers level...
  716. if (level == null)
  717. {
  718. level = _levelListViewController.selectedLevel;
  719. }
  720. // abort!
  721. if (level == null || _bmpValueTextRect == null)
  722. {
  723. _log.Debug("Aborting RefreshScoreSaberData()");
  724. return;
  725. }
  726. // display pp potentially
  727. if (this._model.LevelIdToScoreSaberData != null && this._levelDifficultyViewController.selectedDifficultyLevel != null)
  728. {
  729. if (this._ppText == null)
  730. {
  731. // Create the PP and Star rating labels
  732. //RectTransform bmpTextRect = Resources.FindObjectsOfTypeAll<RectTransform>().First(x => x.name == "BPMText");
  733. var text = UIBuilder.CreateText(this._levelDetailViewController.rectTransform, "PP", new Vector2(-5, -41), new Vector2(20f, 10f));
  734. text.fontSize = 3.5f;
  735. text.alignment = TextAlignmentOptions.Left;
  736. text = UIBuilder.CreateText(this._levelDetailViewController.rectTransform, "STAR", new Vector2(-5, -22), new Vector2(20f, 10f));
  737. text.fontSize = 3.5f;
  738. text.alignment = TextAlignmentOptions.Left;
  739. //NJS Display
  740. _nText = UIBuilder.CreateText(this._levelDetailViewController.rectTransform, "", new Vector2(_bmpValueTextRect.anchoredPosition.x, -29.6f), new Vector2(58f, 10f));
  741. _nText.fontSize = 3.5f;
  742. _nText.alignment = TextAlignmentOptions.Right;
  743. _ppText = UIBuilder.CreateText(this._levelDetailViewController.rectTransform, "?", new Vector2(_bmpValueTextRect.anchoredPosition.x, -41), new Vector2(39f, 10f));
  744. _ppText.fontSize = 3.5f;
  745. _ppText.alignment = TextAlignmentOptions.Right;
  746. _starText = UIBuilder.CreateText(this._levelDetailViewController.rectTransform, "", new Vector2(_bmpValueTextRect.anchoredPosition.x, -22), new Vector2(39f, 10f));
  747. _starText.fontSize = 3.5f;
  748. _starText.alignment = TextAlignmentOptions.Right;
  749. }
  750. LevelDifficulty difficulty = this._levelDifficultyViewController.selectedDifficultyLevel.difficulty;
  751. string njsText;
  752. string difficultyString = difficulty.ToString();
  753. //Grab NJS for difficulty
  754. //Default to 10 if a standard level
  755. float njs = 0;
  756. if (level.levelID.StartsWith("Level"))
  757. {
  758. njsText = "OST";
  759. }
  760. else
  761. {//Grab njs from custom level
  762. SongLoaderPlugin.OverrideClasses.CustomLevel customLevel = _model.LevelIdToCustomSongInfos[level.levelID];
  763. foreach (var diffLevel in customLevel.customSongInfo.difficultyLevels)
  764. {
  765. if (diffLevel.difficulty == difficultyString)
  766. {
  767. GetNoteJump(diffLevel.json, out njs);
  768. }
  769. }
  770. //Show note jump speedS
  771. njsText = "NJS " + njs.ToString();
  772. }
  773. _nText.text = njsText;
  774. _log.Debug("Checking if have info for song {0}", level.songName);
  775. if (this._model.LevelIdToScoreSaberData.ContainsKey(level.levelID))
  776. {
  777. _log.Debug("Checking if have difficulty for song {0} difficulty {1}", level.songName, difficultyString);
  778. ScoreSaberData ppData = this._model.LevelIdToScoreSaberData[level.levelID];
  779. if (ppData.difficultyToSaberDifficulty.ContainsKey(difficultyString))
  780. {
  781. _log.Debug("Display pp for song.");
  782. float pp = ppData.difficultyToSaberDifficulty[difficultyString].pp;
  783. float star = ppData.difficultyToSaberDifficulty[difficultyString].star;
  784. _ppText.SetText(String.Format("{0:0.##}", pp));
  785. _starText.SetText(String.Format("{0:0.##}", star));
  786. }
  787. else
  788. {
  789. _ppText.SetText("?");
  790. _starText.SetText("?");
  791. }
  792. }
  793. else
  794. {
  795. _ppText.SetText("?");
  796. _starText.SetText("?");
  797. }
  798. }
  799. }
  800. /// <summary>
  801. /// Update interactive state of the quick scroll buttons.
  802. /// </summary>
  803. private void RefreshQuickScrollButtons()
  804. {
  805. // if you are ever viewing the song list with less than 5 songs the up/down buttons do not exist.
  806. // just try and fetch them and ignore the exception.
  807. if (_tableViewPageUpButton == null)
  808. {
  809. try
  810. {
  811. _tableViewPageUpButton = _tableViewRectTransform.GetComponentsInChildren<Button>().First(x => x.name == "PageUpButton");
  812. (_tableViewPageUpButton.transform as RectTransform).anchoredPosition = new Vector2(0f, -1f);
  813. }
  814. catch (Exception)
  815. {
  816. // We don't care if this fails.
  817. return;
  818. }
  819. }
  820. if (_tableViewPageDownButton == null)
  821. {
  822. try
  823. {
  824. _tableViewPageDownButton = _tableViewRectTransform.GetComponentsInChildren<Button>().First(x => x.name == "PageDownButton");
  825. (_tableViewPageDownButton.transform as RectTransform).anchoredPosition = new Vector2(0f, 1f);
  826. }
  827. catch (Exception)
  828. {
  829. // We don't care if this fails.
  830. return;
  831. }
  832. }
  833. // Refresh the fast scroll buttons
  834. if (_tableViewPageUpButton != null)
  835. {
  836. _pageUpFastButton.interactable = _tableViewPageUpButton.interactable;
  837. _pageUpFastButton.gameObject.SetActive(_tableViewPageUpButton.IsActive());
  838. }
  839. if (_tableViewPageDownButton != null)
  840. {
  841. _pageDownFastButton.interactable = _tableViewPageDownButton.interactable;
  842. _pageDownFastButton.gameObject.SetActive(_tableViewPageDownButton.IsActive());
  843. }
  844. }
  845. /// <summary>
  846. /// Helper to quickly refresh add to favorites button
  847. /// </summary>
  848. /// <param name="levelId"></param>
  849. private void RefreshAddFavoriteButton(String levelId)
  850. {
  851. if (levelId == null)
  852. {
  853. _currentAddFavoriteButtonSprite = null;
  854. }
  855. else
  856. {
  857. if (_model.Settings.Favorites.Contains(levelId))
  858. {
  859. _currentAddFavoriteButtonSprite = _removeFavoriteSprite;
  860. }
  861. else
  862. {
  863. _currentAddFavoriteButtonSprite = _addFavoriteSprite;
  864. }
  865. }
  866. UIBuilder.SetButtonIcon(ref _addFavoriteButton, _currentAddFavoriteButtonSprite);
  867. }
  868. /// <summary>
  869. /// Adjust the UI colors.
  870. /// </summary>
  871. public void RefreshSortButtonUI()
  872. {
  873. // So far all we need to refresh is the sort buttons.
  874. foreach (SongSortButton sortButton in _sortButtonGroup)
  875. {
  876. UIBuilder.SetButtonBorder(ref sortButton.Button, Color.black);
  877. if (sortButton.SortMode == _model.Settings.sortMode)
  878. {
  879. if (_model.InvertingResults)
  880. {
  881. UIBuilder.SetButtonBorder(ref sortButton.Button, Color.red);
  882. }
  883. else
  884. {
  885. UIBuilder.SetButtonBorder(ref sortButton.Button, Color.green);
  886. }
  887. }
  888. }
  889. // refresh filter buttons
  890. foreach (SongFilterButton filterButton in _filterButtonGroup)
  891. {
  892. UIBuilder.SetButtonBorder(ref filterButton.Button, Color.clear);
  893. if (filterButton.FilterMode == _model.Settings.filterMode)
  894. {
  895. UIBuilder.SetButtonBorder(ref filterButton.Button, Color.green);
  896. }
  897. }
  898. }
  899. /// <summary>
  900. /// Refresh the UI state of any directory buttons.
  901. /// </summary>
  902. public void RefreshDirectoryButtons()
  903. {
  904. // bail if no button, likely folder support not enabled.
  905. if (_upFolderButton == null)
  906. {
  907. return;
  908. }
  909. if (_model.DirStackSize > 1)
  910. {
  911. _upFolderButton.interactable = true;
  912. }
  913. else
  914. {
  915. _upFolderButton.interactable = false;
  916. }
  917. }
  918. /// <summary>
  919. /// Try to refresh the song list. Broken for now.
  920. /// </summary>
  921. public void RefreshSongList()
  922. {
  923. _log.Info("Refreshing the song list view.");
  924. try
  925. {
  926. if (_model.SortedSongList == null)
  927. {
  928. _log.Debug("Songs are not sorted yet, nothing to refresh.");
  929. return;
  930. }
  931. StandardLevelSO[] levels = _model.SortedSongList.ToArray();
  932. StandardLevelListViewController songListViewController = this._levelSelectionFlowCoordinator.GetPrivateField<StandardLevelListViewController>("_levelListViewController");
  933. ReflectionUtil.SetPrivateField(_levelListTableView, "_levels", levels);
  934. ReflectionUtil.SetPrivateField(songListViewController, "_levels", levels);
  935. TableView tableView = ReflectionUtil.GetPrivateField<TableView>(_levelListTableView, "_tableView");
  936. tableView.ReloadData();
  937. String selectedLevelID = null;
  938. if (_model.LastSelectedLevelId != null)
  939. {
  940. selectedLevelID = _model.LastSelectedLevelId;
  941. _log.Debug("Scrolling to row for level ID: {0}", selectedLevelID);
  942. }
  943. else
  944. {
  945. if (levels.Length > 0)
  946. {
  947. selectedLevelID = levels.FirstOrDefault().levelID;
  948. }
  949. }
  950. // HACK, seems like if 6 or less items scrolling to row causes the song list to disappear.
  951. if (levels.Length > 6 && !String.IsNullOrEmpty(selectedLevelID) && levels.Any(x => x.levelID == selectedLevelID))
  952. {
  953. SelectAndScrollToLevel(_levelListTableView, selectedLevelID);
  954. }
  955. RefreshSortButtonUI();
  956. RefreshQuickScrollButtons();
  957. }
  958. catch (Exception e)
  959. {
  960. _log.Exception("Exception refreshing song list:", e);
  961. }
  962. }
  963. /// <summary>
  964. /// Scroll TableView to proper row, fire events.
  965. /// </summary>
  966. /// <param name="table"></param>
  967. /// <param name="levelID"></param>
  968. private void SelectAndScrollToLevel(StandardLevelListTableView table, string levelID)
  969. {
  970. // Check once per load
  971. if (!_checkedForTwitchPlugin)
  972. {
  973. _log.Info("Checking for BeatSaber Twitch Integration Plugin...");
  974. // Try to detect BeatSaber Twitch Integration Plugin
  975. _detectedTwitchPluginQueue = Resources.FindObjectsOfTypeAll<VRUIViewController>().Any(x => x.name == "RequestInfo");
  976. _log.Info("BeatSaber Twitch Integration plugin detected: " + _detectedTwitchPluginQueue);
  977. _checkedForTwitchPlugin = true;
  978. }
  979. // Skip scrolling to level if twitch plugin has queue active.
  980. if (_detectedTwitchPluginQueue)
  981. {
  982. _log.Debug("Skipping SelectAndScrollToLevel() because we detected Twitch Integrtion Plugin has a Queue active...");
  983. return;
  984. }
  985. int row = table.RowNumberForLevelID(levelID);
  986. TableView tableView = table.GetComponentInChildren<TableView>();
  987. tableView.SelectRow(row, true);
  988. tableView.ScrollToRow(row, true);
  989. _lastRow = row;
  990. }
  991. /// <summary>
  992. /// Helper for updating the model (which updates the song list)c
  993. /// </summary>
  994. public void UpdateSongList()
  995. {
  996. _log.Trace("UpdateSongList()");
  997. GameplayMode gameplayMode = _levelSelectionFlowCoordinator.GetPrivateField<GameplayMode>("_gameplayMode");
  998. _model.UpdateSongLists(gameplayMode);
  999. this.RefreshDirectoryButtons();
  1000. }
  1001. /// <summary>
  1002. /// Not normally called by the game-engine. Dependent on SongBrowserApplication to call it.
  1003. /// </summary>
  1004. public void LateUpdate()
  1005. {
  1006. CheckDebugUserInput();
  1007. }
  1008. //Pull njs from a difficulty, based on private function from SongLoader
  1009. public void GetNoteJump(string json, out float noteJumpSpeed)
  1010. {
  1011. noteJumpSpeed = 0;
  1012. var split = json.Split(':');
  1013. for (var i = 0; i < split.Length; i++)
  1014. {
  1015. if (split[i].Contains("_noteJumpSpeed"))
  1016. {
  1017. noteJumpSpeed = Convert.ToSingle(split[i + 1].Split(',')[0], CultureInfo.InvariantCulture);
  1018. }
  1019. }
  1020. }
  1021. /// <summary>
  1022. /// Map some key presses directly to UI interactions to make testing easier.
  1023. /// </summary>
  1024. private void CheckDebugUserInput()
  1025. {
  1026. try
  1027. {
  1028. if (this._levelListViewController != null && this._levelListViewController.isActiveAndEnabled)
  1029. {
  1030. bool isShiftKeyDown = Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift);
  1031. // back
  1032. if (Input.GetKeyDown(KeyCode.Escape))
  1033. {
  1034. this._levelSelectionNavigationController.DismissButtonWasPressed();
  1035. }
  1036. // cycle sort modes
  1037. if (Input.GetKeyDown(KeyCode.T))
  1038. {
  1039. _sortButtonLastPushedIndex = (_sortButtonLastPushedIndex + 1) % _sortButtonGroup.Count;
  1040. _sortButtonGroup[_sortButtonLastPushedIndex].Button.onClick.Invoke();
  1041. }
  1042. // select current sort mode again (toggle inverting)
  1043. if (Input.GetKeyDown(KeyCode.Y))
  1044. {
  1045. _sortButtonGroup[_sortButtonLastPushedIndex].Button.onClick.Invoke();
  1046. }
  1047. // filter playlists
  1048. if (Input.GetKeyDown(KeyCode.P))
  1049. {
  1050. OnPlaylistButtonClickEvent();
  1051. }
  1052. // filter search
  1053. if (Input.GetKeyDown(KeyCode.S))
  1054. {
  1055. OnSearchButtonClickEvent();
  1056. }
  1057. // filter favorites
  1058. if (Input.GetKeyDown(KeyCode.F))
  1059. {
  1060. OnFavoriteFilterButtonClickEvent();
  1061. }
  1062. // delete
  1063. if (Input.GetKeyDown(KeyCode.D))
  1064. {
  1065. _deleteButton.onClick.Invoke();
  1066. }
  1067. // c - select difficulty for top song
  1068. if (Input.GetKeyDown(KeyCode.C))
  1069. {
  1070. this.SelectAndScrollToLevel(_levelListTableView, _model.SortedSongList[0].levelID);
  1071. this._levelDifficultyViewController.HandleDifficultyTableViewDidSelectRow(null, 0);
  1072. this._levelSelectionFlowCoordinator.HandleDifficultyViewControllerDidSelectDifficulty(_levelDifficultyViewController, _model.SortedSongList[0].GetDifficultyLevel(LevelDifficulty.Easy));
  1073. }
  1074. // v start a song or enter a folder
  1075. if (Input.GetKeyDown(KeyCode.Return))
  1076. {
  1077. if (_playButton.isActiveAndEnabled)
  1078. {
  1079. _playButton.onClick.Invoke();
  1080. }
  1081. else if (_enterFolderButton.isActiveAndEnabled)
  1082. {
  1083. _enterFolderButton.onClick.Invoke();
  1084. }
  1085. }
  1086. // backspace - up a folder
  1087. if (Input.GetKeyDown(KeyCode.Backspace))
  1088. {
  1089. _upFolderButton.onClick.Invoke();
  1090. }
  1091. // change song index
  1092. if (isShiftKeyDown && Input.GetKeyDown(KeyCode.N))
  1093. {
  1094. _pageUpFastButton.onClick.Invoke();
  1095. }
  1096. else if (Input.GetKeyDown(KeyCode.N))
  1097. {
  1098. _lastRow = (_lastRow - 1) != -1 ? (_lastRow - 1) % this._model.SortedSongList.Count : 0;
  1099. this.SelectAndScrollToLevel(_levelListTableView, _model.SortedSongList[_lastRow].levelID);
  1100. }
  1101. if (isShiftKeyDown && Input.GetKeyDown(KeyCode.M))
  1102. {
  1103. _pageDownFastButton.onClick.Invoke();
  1104. }
  1105. else if (Input.GetKeyDown(KeyCode.M))
  1106. {
  1107. _lastRow = (_lastRow + 1) % this._model.SortedSongList.Count;
  1108. this.SelectAndScrollToLevel(_levelListTableView, _model.SortedSongList[_lastRow].levelID);
  1109. }
  1110. // add to favorites
  1111. if (Input.GetKeyDown(KeyCode.KeypadPlus))
  1112. {
  1113. ToggleSongInFavorites();
  1114. }
  1115. }
  1116. else if (_deleteDialog != null && _deleteDialog.isInViewControllerHierarchy)
  1117. {
  1118. // accept delete
  1119. if (Input.GetKeyDown(KeyCode.Return))
  1120. {
  1121. _deleteDialog.GetPrivateField<TextMeshProButton>("_okButton").button.onClick.Invoke();
  1122. }
  1123. if (Input.GetKeyDown(KeyCode.Escape))
  1124. {
  1125. _deleteDialog.GetPrivateField<TextMeshProButton>("_cancelButton").button.onClick.Invoke();
  1126. }
  1127. }
  1128. }
  1129. catch (Exception e)
  1130. {
  1131. _log.Exception("Debug Input caused Exception: ", e);
  1132. }
  1133. }
  1134. }
  1135. }